/*
 PlayCanvas Engine v0.203.3 revision c7845b5
 http://playcanvas.com
 Copyright 2011-2016 PlayCanvas Ltd. All rights reserved.
 Do not distribute.
 Contains: https://github.com/tildeio/rsvp.js - see page for license information
*/
var pc = {version:"0.203.3", revision:"c7845b5", config:{}, common:{}, apps:{}, data:{}, unpack:function() {
  console.warn("pc.unpack has been deprecated and will be removed shortly. Please update your code.");
}, makeArray:function(arr) {
  var i, ret = [], length = arr.length;
  for (i = 0;i < length;++i) {
    ret.push(arr[i]);
  }
  return ret;
}, type:function(obj) {
  if (obj === null) {
    return "null";
  }
  var type = typeof obj;
  if (type == "undefined" || type == "number" || type == "string" || type == "boolean") {
    return type;
  }
  return _typeLookup[Object.prototype.toString.call(obj)];
}, extend:function(target, ex) {
  var prop, copy;
  for (prop in ex) {
    copy = ex[prop];
    if (pc.type(copy) == "object") {
      target[prop] = pc.extend({}, copy);
    } else {
      if (pc.type(copy) == "array") {
        target[prop] = pc.extend([], copy);
      } else {
        target[prop] = copy;
      }
    }
  }
  return target;
}, isDefined:function(o) {
  var a;
  return o !== a;
}};
var _typeLookup = function() {
  var result = {}, index, names = ["Array", "Object", "Function", "Date", "RegExp", "Float32Array"];
  for (index = 0;index < names.length;++index) {
    result["[object " + names[index] + "]"] = names[index].toLowerCase();
  }
  return result;
}();
if (typeof exports !== "undefined") {
  exports.pc = pc;
}
;(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] + "CancelRequestAnimationFrame"];
  }
  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);
    };
  }
})();
if (!String.prototype.startsWith) {
  Object.defineProperty(String.prototype, "startsWith", {enumerable:false, configurable:true, writable:true, value:function(str) {
    var that = this;
    for (var i = 0, ceil = str.length;i < ceil;i++) {
      if (that[i] !== str[i]) {
        return false;
      }
    }
    return true;
  }});
}
if (!String.prototype.endsWith) {
  Object.defineProperty(String.prototype, "endsWith", {enumerable:false, configurable:true, writable:true, value:function(str) {
    var that = this;
    for (var i = 0, ceil = str.length;i < ceil;i++) {
      if (that[i + that.length - ceil] !== str[i]) {
        return false;
      }
    }
    return true;
  }});
}
if (!String.prototype.includes) {
  String.prototype.includes = function(search, start) {
    if (typeof start !== "number") {
      start = 0;
    }
    if (start + search.length > this.length) {
      return false;
    } else {
      return this.indexOf(search, start) !== -1;
    }
  };
}
;(function() {
  if (typeof document === "undefined") {
    return;
  }
  var fullscreenchange = function(event) {
    var e = document.createEvent("CustomEvent");
    e.initCustomEvent("fullscreenchange", true, false, null);
    event.target.dispatchEvent(e);
  };
  var fullscreenerror = function(event) {
    var e = document.createEvent("CustomEvent");
    e.initCustomEvent("fullscreenerror", true, false, null);
    event.target.dispatchEvent(e);
  };
  document.addEventListener("webkitfullscreenchange", fullscreenchange, false);
  document.addEventListener("mozfullscreenchange", fullscreenchange, false);
  document.addEventListener("MSFullscreenChange", fullscreenchange, false);
  document.addEventListener("webkitfullscreenerror", fullscreenerror, false);
  document.addEventListener("mozfullscreenerror", fullscreenerror, false);
  document.addEventListener("MSFullscreenError", fullscreenerror, false);
  if (Element.prototype.mozRequestFullScreen) {
    Element.prototype.requestFullscreen = function() {
      this.mozRequestFullScreen();
    };
  } else {
    Element.prototype.requestFullscreen = Element.prototype.requestFullscreen || Element.prototype.webkitRequestFullscreen || Element.prototype.msRequestFullscreen;
  }
  document.exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen;
  if (!document.hasOwnProperty("fullscreenElement")) {
    Object.defineProperty(document, "fullscreenElement", {enumerable:true, configurable:false, get:function() {
      return document.webkitCurrentFullScreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
    }});
  }
  if (!document.hasOwnProperty("fullscreenEnabled")) {
    Object.defineProperty(document, "fullscreenEnabled", {enumerable:true, configurable:false, get:function() {
      return document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled;
    }});
  }
})();
pc.extend(pc, function() {
  var Color = function(r, g, b, a) {
    this.buffer = new ArrayBuffer(4 * 4);
    this.data = new Float32Array(this.buffer, 0, 4);
    this.data3 = new Float32Array(this.buffer, 0, 3);
    this.data[0] = r || 0;
    this.data[1] = g || 0;
    this.data[2] = b || 0;
    this.data[3] = a !== undefined ? a : 1;
  };
  Color.prototype = {clone:function() {
    return new pc.Color(this.data[0], this.data[1], this.data[2], this.data[3]);
  }, copy:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] = b[0];
    a[1] = b[1];
    a[2] = b[2];
    a[3] = b[3];
    return this;
  }, set:function(r, g, b, a) {
    var c = this.data;
    c[0] = r;
    c[1] = g;
    c[2] = b;
    c[3] = a === undefined ? 1 : a;
    return this;
  }, fromString:function(hex) {
    var i = parseInt(hex.replace("#", "0x"));
    var bytes;
    if (hex.length > 7) {
      bytes = pc.math.intToBytes32(i);
    } else {
      bytes = pc.math.intToBytes24(i);
      bytes[3] = 255;
    }
    this.set(bytes[0] / 255, bytes[1] / 255, bytes[2] / 255, bytes[3] / 255);
    return this;
  }, toString:function(alpha) {
    var s = "#" + ((1 << 24) + (parseInt(this.r * 255) << 16) + (parseInt(this.g * 255) << 8) + parseInt(this.b * 255)).toString(16).slice(1);
    if (alpha === true) {
      var a = parseInt(this.a * 255).toString(16);
      if (this.a < 16 / 255) {
        s += "0" + a;
      } else {
        s += a;
      }
    }
    return s;
  }};
  Object.defineProperty(Color.prototype, "r", {get:function() {
    return this.data[0];
  }, set:function(value) {
    this.data[0] = value;
  }});
  Object.defineProperty(Color.prototype, "g", {get:function() {
    return this.data[1];
  }, set:function(value) {
    this.data[1] = value;
  }});
  Object.defineProperty(Color.prototype, "b", {get:function() {
    return this.data[2];
  }, set:function(value) {
    this.data[2] = value;
  }});
  Object.defineProperty(Color.prototype, "a", {get:function() {
    return this.data[3];
  }, set:function(value) {
    this.data[3] = value;
  }});
  return {Color:Color};
}());
pc.guid = function() {
  return {create:function() {
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
      var r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8;
      return v.toString(16);
    });
  }};
}();
pc.extend(pc, function() {
  var Timer = function Timer() {
    this._isRunning = false;
    this._a = 0;
    this._b = 0;
  };
  Timer.prototype = {start:function() {
    this._isRunning = true;
    this._a = (new Date).getTime();
  }, stop:function() {
    this._isRunning = false;
    this._b = (new Date).getTime();
  }, getMilliseconds:function() {
    return this._b - this._a;
  }};
  return {Timer:Timer, now:!window.performance || !window.performance.now || !window.performance.timing ? Date.now : function() {
    return window.performance.now();
  }};
}());
pc.extend(pc, function() {
  return {createURI:function(options) {
    var s = "";
    if ((options.authority || options.scheme) && (options.host || options.hostpath)) {
      throw new Error("Can't have 'scheme' or 'authority' and 'host' or 'hostpath' option");
    }
    if (options.host && options.hostpath) {
      throw new Error("Can't have 'host' and 'hostpath' option");
    }
    if (options.path && options.hostpath) {
      throw new Error("Can't have 'path' and 'hostpath' option");
    }
    if (options.scheme) {
      s += options.scheme + ":";
    }
    if (options.authority) {
      s += "//" + options.authority;
    }
    if (options.host) {
      s += options.host;
    }
    if (options.path) {
      s += options.path;
    }
    if (options.hostpath) {
      s += options.hostpath;
    }
    if (options.query) {
      s += "?" + options.query;
    }
    if (options.fragment) {
      s += "#" + options.fragment;
    }
    return s;
  }, URI:function(uri) {
    var re = /^(([^:\/?\#]+):)?(\/\/([^\/?\#]*))?([^?\#]*)(\?([^\#]*))?(\#(.*))?/, result = uri.match(re);
    this.scheme = result[2];
    this.authority = result[4];
    this.path = result[5];
    this.query = result[7];
    this.fragment = result[9];
    this.toString = function() {
      var s = "";
      if (this.scheme) {
        s += this.scheme + ":";
      }
      if (this.authority) {
        s += "//" + this.authority;
      }
      s += this.path;
      if (this.query) {
        s += "?" + this.query;
      }
      if (this.fragment) {
        s += "#" + this.fragment;
      }
      return s;
    };
    this.getQuery = function() {
      var vars;
      var pair;
      var result = {};
      if (this.query) {
        vars = decodeURIComponent(this.query).split("&");
        vars.forEach(function(item, index, arr) {
          pair = item.split("=");
          result[pair[0]] = pair[1];
        }, this);
      }
      return result;
    };
    this.setQuery = function(params) {
      var q = "";
      for (var key in params) {
        if (params.hasOwnProperty(key)) {
          if (q !== "") {
            q += "&";
          }
          q += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
        }
      }
      this.query = q;
    };
  }};
}());
pc.extend(pc, function() {
  var log = {write:function(text) {
    console.log(text);
  }, open:function(text) {
    pc.log.write("Powered by PlayCanvas " + pc.version + " " + pc.revision);
  }, info:function(text) {
    console.info("INFO:    " + text);
  }, debug:function(text) {
    console.debug("DEBUG:   " + text);
  }, error:function(text) {
    console.error("ERROR:   " + text);
  }, warning:function(text) {
    console.warn("WARNING: " + text);
  }, alert:function(text) {
    pc.log.write("ALERT:   " + text);
    alert(text);
  }, assert:function(condition, text) {
    if (condition === false) {
      pc.log.write("ASSERT:  " + text);
      alert("ASSERT failed: " + text);
    }
  }};
  return {log:log};
}());
var logINFO = pc.log.info;
var logDEBUG = pc.log.debug;
var logWARNING = pc.log.warning;
var logERROR = pc.log.error;
var logALERT = pc.log.alert;
var logASSERT = pc.log.assert;
Function.prototype.extendsFrom = function(Super) {
  var Self, Func;
  var Temp = function() {
  };
  Self = this;
  Func = function() {
    Super.apply(this, arguments);
    Self.apply(this, arguments);
    this.constructor = Self;
  };
  Func._super = Super.prototype;
  Temp.prototype = Super.prototype;
  Func.prototype = new Temp;
  return Func;
};
pc.extend(pc, function() {
  return {inherits:function(Self, Super) {
    var Temp = function() {
    };
    var Func = function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) {
      Super.call(this, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
      Self.call(this, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
    };
    Func._super = Super.prototype;
    Temp.prototype = Super.prototype;
    Func.prototype = new Temp;
    return Func;
  }};
}());
pc.path = function() {
  return {delimiter:"/", join:function() {
    var index;
    var num = arguments.length;
    var result = arguments[0];
    for (index = 0;index < num - 1;++index) {
      var one = arguments[index];
      var two = arguments[index + 1];
      if (!pc.isDefined(one) || !pc.isDefined(two)) {
        throw new Error("undefined argument to pc.path.join");
      }
      if (two[0] === pc.path.delimiter) {
        result = two;
        continue;
      }
      if (one && two && one[one.length - 1] !== pc.path.delimiter && two[0] !== pc.path.delimiter) {
        result += pc.path.delimiter + two;
      } else {
        result += two;
      }
    }
    return result;
  }, split:function(path) {
    var parts = path.split(pc.path.delimiter);
    var tail = parts.slice(parts.length - 1)[0];
    var head = parts.slice(0, parts.length - 1).join(pc.path.delimiter);
    return [head, tail];
  }, getBasename:function(path) {
    return pc.path.split(path)[1];
  }, getDirectory:function(path) {
    var parts = path.split(pc.path.delimiter);
    return parts.slice(0, parts.length - 1).join(pc.path.delimiter);
  }, getExtension:function(path) {
    var ext = path.split("?")[0].split(".").pop();
    if (ext !== path) {
      return "." + ext;
    } else {
      return "";
    }
  }, isRelativePath:function(s) {
    return s.charAt(0) !== "/" && s.match(/:\/\//) === null;
  }, extractPath:function(s) {
    var path = ".", parts = s.split("/"), i = 0;
    if (parts.length > 1) {
      if (pc.path.isRelativePath(s) === false) {
        path = "";
      }
      for (i = 0;i < parts.length - 1;++i) {
        path += "/" + parts[i];
      }
    }
    return path;
  }};
}();
pc.string = function() {
  return {ASCII_LOWERCASE:"abcdefghijklmnopqrstuvwxyz", ASCII_UPPERCASE:"ABCDEFGHIJKLMNOPQRSTUVWXYZ", ASCII_LETTERS:this.ASCII_LOWERCASE + this.ASCII_UPPERCASE, format:function(s) {
    var i = 0, regexp, args = pc.makeArray(arguments);
    args.shift();
    for (i = 0;i < args.length;i++) {
      regexp = new RegExp("\\{" + i + "\\}", "gi");
      s = s.replace(regexp, args[i]);
    }
    return s;
  }, startsWith:function(s, subs) {
    console.warn("WARNING: startsWith: Function is deprecated. Use String.startsWith instead.");
    return s.startsWith(subs);
  }, endsWith:function(s, subs) {
    console.warn("WARNING: endsWith: Function is deprecated. Use String.endsWith instead.");
    return s.endsWith(subs);
  }, toBool:function(s, strict) {
    if (s === "true") {
      return true;
    }
    if (strict) {
      if (s === "false") {
        return false;
      }
      throw new Error("Not a boolean string");
    }
    return false;
  }};
}();
pc.debug = function() {
  var table = null;
  var row = null;
  var title = null;
  var field = null;
  return {display:function(data) {
    function init() {
      table = document.createElement("table");
      row = document.createElement("tr");
      title = document.createElement("td");
      field = document.createElement("td");
      table.style.cssText = "position:absolute;font-family:sans-serif;font-size:12px;color:#cccccc";
      table.style.top = "0px";
      table.style.left = "0px";
      table.style.border = "thin solid #cccccc";
      document.body.appendChild(table);
    }
    if (!table) {
      init();
    }
    table.innerHTML = "";
    for (var key in data) {
      var r = row.cloneNode();
      var t = title.cloneNode();
      var f = field.cloneNode();
      t.textContent = key;
      f.textContent = data[key];
      r.appendChild(t);
      r.appendChild(f);
      table.appendChild(r);
    }
  }};
}();
pc.events = function() {
  var Events = {attach:function(target) {
    var ev = pc.events;
    target.on = ev.on;
    target.off = ev.off;
    target.fire = ev.fire;
    target.once = ev.once;
    target.hasEvent = ev.hasEvent;
    target.bind = ev.on;
    target.unbind = ev.off;
    return target;
  }, on:function(name, callback, scope) {
    if (pc.type(name) != "string") {
      throw new TypeError("Event name must be a string");
    }
    var callbacks = this._callbacks || (this._callbacks = {});
    var events = callbacks[name] || (callbacks[name] = []);
    events.push({callback:callback, scope:scope || this});
    return this;
  }, off:function(name, callback, scope) {
    var callbacks = this._callbacks;
    var events;
    var index;
    if (!callbacks) {
      return;
    }
    if (!callback) {
      callbacks[name] = [];
    } else {
      events = callbacks[name];
      if (!events) {
        return this;
      }
      for (index = 0;index < events.length;index++) {
        if (events[index].callback === callback) {
          if (!scope || scope === events[index].scope) {
            events.splice(index, 1);
            index--;
          }
        }
      }
    }
    return this;
  }, fire:function(name, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) {
    var index;
    var length;
    var args;
    var callbacks;
    if (this._callbacks && this._callbacks[name]) {
      length = this._callbacks[name].length;
      if (length) {
        callbacks = this._callbacks[name].slice();
        var originalIndex = 0;
        for (index = 0;index < length;++index) {
          var scope = callbacks[index].scope;
          callbacks[index].callback.call(scope, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
          if (callbacks[index].callback.once) {
            this._callbacks[name].splice(originalIndex, 1);
          } else {
            originalIndex++;
          }
        }
      }
    }
    return this;
  }, once:function(name, callback, scope) {
    callback.once = true;
    this.on(name, callback, scope);
  }, hasEvent:function(name) {
    return this._callbacks !== undefined && this._callbacks[name] !== undefined && this._callbacks[name].length > 0;
  }};
  Events.bind = Events.on;
  Events.unbind = Events.off;
  return Events;
}();
pc.extend(pc, function() {
  var TagsCache = function(key) {
    this._index = {};
    this._key = key || null;
  };
  TagsCache.prototype = {addItem:function(item) {
    var tags = item.tags._list;
    for (var i = 0;i < tags.length;i++) {
      this.add(tags[i], item);
    }
  }, removeItem:function(item) {
    var tags = item.tags._list;
    for (var i = 0;i < tags.length;i++) {
      this.remove(tags[i], item);
    }
  }, add:function(tag, item) {
    if (this._index[tag] && this._index[tag].list.indexOf(item) !== -1) {
      return;
    }
    if (!this._index[tag]) {
      this._index[tag] = {list:[]};
      if (this._key) {
        this._index[tag].keys = {};
      }
    }
    this._index[tag].list.push(item);
    if (this._key) {
      this._index[tag].keys[item[this._key]] = item;
    }
  }, remove:function(tag, item) {
    if (!this._index[tag]) {
      return;
    }
    if (this._key) {
      if (!this._index[tag].keys[item[this._key]]) {
        return;
      }
    }
    var ind = this._index[tag].indexOf(item);
    if (ind === -1) {
      return;
    }
    this._index[tag].list.splice(ind, 1);
    if (this._key) {
      delete this._index[tag].keys[item[this._key]];
    }
    if (this._index[tag].list.length === 0) {
      delete this._index[tag];
    }
  }, find:function(args) {
    var self = this;
    var index = {};
    var items = [];
    var i, n, t;
    var item, tag, tags, tagsRest, list, missingIndex;
    var sort = function(a, b) {
      return self._index[a].list.length - self._index[b].list.length;
    };
    for (i = 0;i < args.length;i++) {
      tag = args[i];
      if (tag instanceof Array) {
        if (tag.length === 0) {
          continue;
        }
        if (tag.length === 1) {
          tag = tag[0];
        } else {
          missingIndex = false;
          for (t = 0;t < tag.length;t++) {
            if (!this._index[tag[t]]) {
              missingIndex = true;
              break;
            }
          }
          if (missingIndex) {
            continue;
          }
          tags = tag.slice(0).sort(sort);
          tagsRest = tags.slice(1);
          if (tagsRest.length === 1) {
            tagsRest = tagsRest[0];
          }
          for (n = 0;n < this._index[tags[0]].list.length;n++) {
            item = this._index[tags[0]].list[n];
            if ((this._key ? !index[item[this._key]] : items.indexOf(item) === -1) && item.tags.has(tagsRest)) {
              if (this._key) {
                index[item[this._key]] = true;
              }
              items.push(item);
            }
          }
          continue;
        }
      }
      if (tag && typeof tag === "string" && this._index[tag]) {
        for (n = 0;n < this._index[tag].list.length;n++) {
          item = this._index[tag].list[n];
          if (this._key) {
            if (!index[item[this._key]]) {
              index[item[this._key]] = true;
              items.push(item);
            }
          } else {
            if (items.indexOf(item) === -1) {
              items.push(item);
            }
          }
        }
      }
    }
    return items;
  }};
  var Tags = function(parent) {
    this._index = {};
    this._list = [];
    this._parent = parent;
    pc.events.attach(this);
  };
  Tags.prototype = {add:function() {
    var changed = false;
    var tags = this._processArguments(arguments, true);
    if (!tags.length) {
      return changed;
    }
    for (var i = 0;i < tags.length;i++) {
      if (this._index[tags[i]]) {
        continue;
      }
      changed = true;
      this._index[tags[i]] = true;
      this._list.push(tags[i]);
      this.fire("add", tags[i], this._parent);
    }
    if (changed) {
      this.fire("change", this._parent);
    }
    return changed;
  }, remove:function() {
    var changed = false;
    if (!this._list.length) {
      return changed;
    }
    var tags = this._processArguments(arguments, true);
    if (!tags.length) {
      return changed;
    }
    for (var i = 0;i < tags.length;i++) {
      if (!this._index[tags[i]]) {
        continue;
      }
      changed = true;
      delete this._index[tags[i]];
      this._list.splice(this._list.indexOf(tags[i]), 1);
      this.fire("remove", tags[i], this._parent);
    }
    if (changed) {
      this.fire("change", this._parent);
    }
    return changed;
  }, clear:function() {
    if (!this._list.length) {
      return;
    }
    var tags = this._list.slice(0);
    this._list = [];
    this._index = {};
    for (var i = 0;i < tags.length;i++) {
      this.fire("remove", tags[i], this._parent);
    }
    this.fire("change", this._parent);
  }, has:function() {
    if (!this._list.length) {
      return false;
    }
    return this._has(this._processArguments(arguments));
  }, _has:function(tags) {
    if (!this._list.length || !tags.length) {
      return false;
    }
    for (var i = 0;i < tags.length;i++) {
      if (tags[i].length === 1) {
        if (this._index[tags[i][0]]) {
          return true;
        }
      } else {
        var multiple = true;
        for (var t = 0;t < tags[i].length;t++) {
          if (this._index[tags[i][t]]) {
            continue;
          }
          multiple = false;
          break;
        }
        if (multiple) {
          return true;
        }
      }
    }
    return false;
  }, list:function() {
    return this._list.slice(0);
  }, _processArguments:function(args, flat) {
    var tags = [];
    var tmp = [];
    if (!args || !args.length) {
      return tags;
    }
    for (var i = 0;i < args.length;i++) {
      if (args[i] instanceof Array) {
        if (!flat) {
          tmp = [];
        }
        for (var t = 0;t < args[i].length;t++) {
          if (typeof args[i][t] !== "string") {
            continue;
          }
          if (flat) {
            tags.push(args[i][t]);
          } else {
            tmp.push(args[i][t]);
          }
        }
        if (!flat && tmp.length) {
          tags.push(tmp);
        }
      } else {
        if (typeof args[i] === "string") {
          if (flat) {
            tags.push(args[i]);
          } else {
            tags.push([args[i]]);
          }
        }
      }
    }
    return tags;
  }};
  Object.defineProperty(Tags.prototype, "size", {get:function() {
    return this._list.length;
  }});
  return {TagsCache:TagsCache, Tags:Tags};
}());
pc.extend(pc, function() {
  var AllocatePool = function(constructor, size) {
    this._constructor = constructor;
    this._pool = [];
    this._count = 0;
    this._resize(size);
  };
  AllocatePool.prototype = {_resize:function(size) {
    if (size > this._pool.length) {
      for (var i = this._pool.length;i < size;i++) {
        this._pool[i] = new this._constructor;
      }
    }
  }, allocate:function() {
    if (this._count >= this._pool.length) {
      this._resize(this._pool.length * 2);
    }
    return this._pool[this._count++];
  }, freeAll:function() {
    this._count = 0;
  }};
  return {AllocatePool:AllocatePool};
}());
pc.math = {DEG_TO_RAD:Math.PI / 180, RAD_TO_DEG:180 / Math.PI, INV_LOG2:1 / Math.log(2), clamp:function(value, min, max) {
  if (value >= max) {
    return max;
  }
  if (value <= min) {
    return min;
  }
  return value;
}, intToBytes24:function(i) {
  var r, g, b;
  r = i >> 16 & 255;
  g = i >> 8 & 255;
  b = i & 255;
  return [r, g, b];
}, intToBytes32:function(i) {
  var r, g, b, a;
  r = i >> 24 & 255;
  g = i >> 16 & 255;
  b = i >> 8 & 255;
  a = i & 255;
  return [r, g, b, a];
}, bytesToInt24:function(r, g, b) {
  if (r.length) {
    b = r[2];
    g = r[1];
    r = r[0];
  }
  return r << 16 | g << 8 | b;
}, bytesToInt32:function(r, g, b, a) {
  if (r.length) {
    a = r[3];
    b = r[2];
    g = r[1];
    r = r[0];
  }
  return (r << 24 | g << 16 | b << 8 | a) >>> 32;
}, lerp:function(a, b, alpha) {
  return a + (b - a) * pc.math.clamp(alpha, 0, 1);
}, lerpAngle:function(a, b, alpha) {
  if (b - a > 180) {
    b -= 360;
  }
  if (b - a < -180) {
    b += 360;
  }
  return pc.math.lerp(a, b, pc.math.clamp(alpha, 0, 1));
}, powerOfTwo:function(x) {
  return x !== 0 && !(x & x - 1);
}, nextPowerOfTwo:function(val) {
  val--;
  val = val >> 1 | val;
  val = val >> 2 | val;
  val = val >> 4 | val;
  val = val >> 8 | val;
  val = val >> 16 | val;
  val++;
  return val;
}, random:function(min, max) {
  var diff = max - min;
  return Math.random() * diff + min;
}, smoothstep:function(min, max, x) {
  if (x <= min) {
    return 0;
  }
  if (x >= max) {
    return 1;
  }
  x = (x - min) / (max - min);
  return x * x * (3 - 2 * x);
}, smootherstep:function(min, max, x) {
  if (x <= min) {
    return 0;
  }
  if (x >= max) {
    return 1;
  }
  x = (x - min) / (max - min);
  return x * x * x * (x * (x * 6 - 15) + 10);
}};
pc.math.intToBytes = pc.math.intToBytes32;
pc.math.bytesToInt = pc.math.bytesToInt32;
if (!Math.log2) {
  Math.log2 = function(x) {
    return Math.log(x) * pc.math.INV_LOG2;
  };
}
;pc.extend(pc, function() {
  var Vec2 = function() {
    this.data = new Float32Array(2);
    if (arguments.length === 2) {
      this.data.set(arguments);
    } else {
      this.data[0] = 0;
      this.data[1] = 0;
    }
  };
  Vec2.prototype = {add:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] += b[0];
    a[1] += b[1];
    return this;
  }, add2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] + b[0];
    r[1] = a[1] + b[1];
    return this;
  }, clone:function() {
    return (new Vec2).copy(this);
  }, copy:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] = b[0];
    a[1] = b[1];
    return this;
  }, dot:function(rhs) {
    var a = this.data, b = rhs.data;
    return a[0] * b[0] + a[1] * b[1];
  }, equals:function(rhs) {
    var a = this.data, b = rhs.data;
    return a[0] === b[0] && a[1] === b[1];
  }, length:function() {
    var v = this.data;
    return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
  }, lengthSq:function() {
    var v = this.data;
    return v[0] * v[0] + v[1] * v[1];
  }, lerp:function(lhs, rhs, alpha) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] + alpha * (b[0] - a[0]);
    r[1] = a[1] + alpha * (b[1] - a[1]);
    return this;
  }, mul:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] *= b[0];
    a[1] *= b[1];
    return this;
  }, mul2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] * b[0];
    r[1] = a[1] * b[1];
    return this;
  }, normalize:function() {
    var v = this.data;
    var lengthSq = v[0] * v[0] + v[1] * v[1];
    if (lengthSq > 0) {
      var invLength = 1 / Math.sqrt(lengthSq);
      v[0] *= invLength;
      v[1] *= invLength;
    }
    return this;
  }, scale:function(scalar) {
    var v = this.data;
    v[0] *= scalar;
    v[1] *= scalar;
    return this;
  }, set:function(x, y) {
    var v = this.data;
    v[0] = x;
    v[1] = y;
    return this;
  }, sub:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] -= b[0];
    a[1] -= b[1];
    return this;
  }, sub2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] - b[0];
    r[1] = a[1] - b[1];
    return this;
  }, toString:function() {
    return "[" + this.data[0] + ", " + this.data[1] + "]";
  }};
  Object.defineProperty(Vec2.prototype, "x", {get:function() {
    return this.data[0];
  }, set:function(value) {
    this.data[0] = value;
  }});
  Object.defineProperty(Vec2.prototype, "y", {get:function() {
    return this.data[1];
  }, set:function(value) {
    this.data[1] = value;
  }});
  Object.defineProperty(Vec2, "ONE", {get:function() {
    var one = new Vec2(1, 1);
    return function() {
      return one;
    };
  }()});
  Object.defineProperty(Vec2, "RIGHT", {get:function() {
    var right = new Vec2(1, 0);
    return function() {
      return right;
    };
  }()});
  Object.defineProperty(Vec2, "UP", {get:function() {
    var down = new Vec2(0, 1);
    return function() {
      return down;
    };
  }()});
  Object.defineProperty(Vec2, "ZERO", {get:function() {
    var zero = new Vec2(0, 0);
    return function() {
      return zero;
    };
  }()});
  return {Vec2:Vec2};
}());
pc.extend(pc, function() {
  var Vec3 = function(x, y, z) {
    this.data = new Float32Array(3);
    this.data[0] = x || 0;
    this.data[1] = y || 0;
    this.data[2] = z || 0;
  };
  Vec3.prototype = {add:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] += b[0];
    a[1] += b[1];
    a[2] += b[2];
    return this;
  }, add2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] + b[0];
    r[1] = a[1] + b[1];
    r[2] = a[2] + b[2];
    return this;
  }, clone:function() {
    return (new Vec3).copy(this);
  }, copy:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] = b[0];
    a[1] = b[1];
    a[2] = b[2];
    return this;
  }, cross:function(lhs, rhs) {
    var a, b, r, ax, ay, az, bx, by, bz;
    a = lhs.data;
    b = rhs.data;
    r = this.data;
    ax = a[0];
    ay = a[1];
    az = a[2];
    bx = b[0];
    by = b[1];
    bz = b[2];
    r[0] = ay * bz - by * az;
    r[1] = az * bx - bz * ax;
    r[2] = ax * by - bx * ay;
    return this;
  }, dot:function(rhs) {
    var a = this.data, b = rhs.data;
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
  }, equals:function(rhs) {
    var a = this.data, b = rhs.data;
    return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
  }, length:function() {
    var v = this.data;
    return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  }, lengthSq:function() {
    var v = this.data;
    return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
  }, lerp:function(lhs, rhs, alpha) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] + alpha * (b[0] - a[0]);
    r[1] = a[1] + alpha * (b[1] - a[1]);
    r[2] = a[2] + alpha * (b[2] - a[2]);
    return this;
  }, mul:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] *= b[0];
    a[1] *= b[1];
    a[2] *= b[2];
    return this;
  }, mul2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] * b[0];
    r[1] = a[1] * b[1];
    r[2] = a[2] * b[2];
    return this;
  }, normalize:function() {
    var v = this.data;
    var lengthSq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
    if (lengthSq > 0) {
      var invLength = 1 / Math.sqrt(lengthSq);
      v[0] *= invLength;
      v[1] *= invLength;
      v[2] *= invLength;
    }
    return this;
  }, project:function(rhs) {
    var a = this.data;
    var b = rhs.data;
    var a_dot_b = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    var b_dot_b = b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
    var s = a_dot_b / b_dot_b;
    a[0] = b[0] * s;
    a[1] = b[1] * s;
    a[2] = b[2] * s;
    return this;
  }, scale:function(scalar) {
    var v = this.data;
    v[0] *= scalar;
    v[1] *= scalar;
    v[2] *= scalar;
    return this;
  }, set:function(x, y, z) {
    var v = this.data;
    v[0] = x;
    v[1] = y;
    v[2] = z;
    return this;
  }, sub:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] -= b[0];
    a[1] -= b[1];
    a[2] -= b[2];
    return this;
  }, sub2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] - b[0];
    r[1] = a[1] - b[1];
    r[2] = a[2] - b[2];
    return this;
  }, toString:function() {
    return "[" + this.data[0] + ", " + this.data[1] + ", " + this.data[2] + "]";
  }};
  Object.defineProperty(Vec3.prototype, "x", {get:function() {
    return this.data[0];
  }, set:function(value) {
    this.data[0] = value;
  }});
  Object.defineProperty(Vec3.prototype, "y", {get:function() {
    return this.data[1];
  }, set:function(value) {
    this.data[1] = value;
  }});
  Object.defineProperty(Vec3.prototype, "z", {get:function() {
    return this.data[2];
  }, set:function(value) {
    this.data[2] = value;
  }});
  Object.defineProperty(Vec3, "BACK", {get:function() {
    var back = new Vec3(0, 0, 1);
    return function() {
      return back;
    };
  }()});
  Object.defineProperty(Vec3, "DOWN", {get:function() {
    var down = new Vec3(0, -1, 0);
    return function() {
      return down;
    };
  }()});
  Object.defineProperty(Vec3, "FORWARD", {get:function() {
    var forward = new Vec3(0, 0, -1);
    return function() {
      return forward;
    };
  }()});
  Object.defineProperty(Vec3, "LEFT", {get:function() {
    var left = new Vec3(-1, 0, 0);
    return function() {
      return left;
    };
  }()});
  Object.defineProperty(Vec3, "ONE", {get:function() {
    var one = new Vec3(1, 1, 1);
    return function() {
      return one;
    };
  }()});
  Object.defineProperty(Vec3, "RIGHT", {get:function() {
    var right = new Vec3(1, 0, 0);
    return function() {
      return right;
    };
  }()});
  Object.defineProperty(Vec3, "UP", {get:function() {
    var down = new Vec3(0, 1, 0);
    return function() {
      return down;
    };
  }()});
  Object.defineProperty(Vec3, "ZERO", {get:function() {
    var zero = new Vec3(0, 0, 0);
    return function() {
      return zero;
    };
  }()});
  return {Vec3:Vec3};
}());
pc.extend(pc, function() {
  var Vec4 = function() {
    this.data = new Float32Array(4);
    if (arguments.length === 4) {
      this.data.set(arguments);
    } else {
      this.data[0] = 0;
      this.data[1] = 0;
      this.data[2] = 0;
      this.data[3] = 0;
    }
  };
  Vec4.prototype = {add:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] += b[0];
    a[1] += b[1];
    a[2] += b[2];
    a[3] += b[3];
    return this;
  }, add2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] + b[0];
    r[1] = a[1] + b[1];
    r[2] = a[2] + b[2];
    r[3] = a[3] + b[3];
    return this;
  }, clone:function() {
    return (new Vec4).copy(this);
  }, copy:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] = b[0];
    a[1] = b[1];
    a[2] = b[2];
    a[3] = b[3];
    return this;
  }, dot:function(rhs) {
    var a = this.data, b = rhs.data;
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
  }, equals:function(rhs) {
    var a = this.data, b = rhs.data;
    return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
  }, length:function() {
    var v = this.data;
    return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]);
  }, lengthSq:function() {
    var v = this.data;
    return v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3];
  }, lerp:function(lhs, rhs, alpha) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] + alpha * (b[0] - a[0]);
    r[1] = a[1] + alpha * (b[1] - a[1]);
    r[2] = a[2] + alpha * (b[2] - a[2]);
    r[3] = a[3] + alpha * (b[3] - a[3]);
    return this;
  }, mul:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] *= b[0];
    a[1] *= b[1];
    a[2] *= b[2];
    a[3] *= b[3];
    return this;
  }, mul2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] * b[0];
    r[1] = a[1] * b[1];
    r[2] = a[2] * b[2];
    r[3] = a[3] * b[3];
    return this;
  }, normalize:function() {
    var v = this.data;
    var lengthSq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3];
    if (lengthSq > 0) {
      var invLength = 1 / Math.sqrt(lengthSq);
      v[0] *= invLength;
      v[1] *= invLength;
      v[2] *= invLength;
      v[3] *= invLength;
    }
    return this;
  }, scale:function(scalar) {
    var v = this.data;
    v[0] *= scalar;
    v[1] *= scalar;
    v[2] *= scalar;
    v[3] *= scalar;
    return this;
  }, set:function(x, y, z, w) {
    var v = this.data;
    v[0] = x;
    v[1] = y;
    v[2] = z;
    v[3] = w;
    return this;
  }, sub:function(rhs) {
    var a = this.data, b = rhs.data;
    a[0] -= b[0];
    a[1] -= b[1];
    a[2] -= b[2];
    a[3] -= b[3];
    return this;
  }, sub2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] - b[0];
    r[1] = a[1] - b[1];
    r[2] = a[2] - b[2];
    r[3] = a[3] - b[3];
    return this;
  }, toString:function() {
    return "[" + this.data[0] + ", " + this.data[1] + ", " + this.data[2] + ", " + this.data[3] + "]";
  }};
  Object.defineProperty(Vec4.prototype, "x", {get:function() {
    return this.data[0];
  }, set:function(value) {
    this.data[0] = value;
  }});
  Object.defineProperty(Vec4.prototype, "y", {get:function() {
    return this.data[1];
  }, set:function(value) {
    this.data[1] = value;
  }});
  Object.defineProperty(Vec4.prototype, "z", {get:function() {
    return this.data[2];
  }, set:function(value) {
    this.data[2] = value;
  }});
  Object.defineProperty(Vec4.prototype, "w", {get:function() {
    return this.data[3];
  }, set:function(value) {
    this.data[3] = value;
  }});
  Object.defineProperty(Vec4, "ONE", {get:function() {
    var one = new Vec4(1, 1, 1, 1);
    return function() {
      return one;
    };
  }()});
  Object.defineProperty(Vec4, "ZERO", {get:function() {
    var zero = new Vec4(0, 0, 0, 0);
    return function() {
      return zero;
    };
  }()});
  return {Vec4:Vec4};
}());
pc.extend(pc, function() {
  var typeNumber = "number";
  var Mat3 = function(v0, v1, v2, v3, v4, v5, v6, v7, v8) {
    this.data = new Float32Array(9);
    if (typeof v0 === typeNumber) {
      this.data[0] = v0;
      this.data[1] = v1;
      this.data[2] = v2;
      this.data[3] = v3;
      this.data[4] = v4;
      this.data[5] = v5;
      this.data[6] = v6;
      this.data[7] = v7;
      this.data[8] = v8;
    } else {
      this.setIdentity();
    }
  };
  Mat3.prototype = {clone:function() {
    return (new pc.Mat3).copy(this);
  }, copy:function(rhs) {
    var src = rhs.data;
    var dst = this.data;
    dst[0] = src[0];
    dst[1] = src[1];
    dst[2] = src[2];
    dst[3] = src[3];
    dst[4] = src[4];
    dst[5] = src[5];
    dst[6] = src[6];
    dst[7] = src[7];
    dst[8] = src[8];
    return this;
  }, equals:function(rhs) {
    var l = this.data;
    var r = rhs.data;
    return l[0] === r[0] && l[1] === r[1] && l[2] === r[2] && l[3] === r[3] && l[4] === r[4] && l[5] === r[5] && l[6] === r[6] && l[7] === r[7] && l[8] === r[8];
  }, isIdentity:function() {
    var m = this.data;
    return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 0 && m[4] === 1 && m[5] === 0 && m[6] === 0 && m[7] === 0 && m[8] === 1;
  }, setIdentity:function() {
    var m = this.data;
    m[0] = 1;
    m[1] = 0;
    m[2] = 0;
    m[3] = 0;
    m[4] = 1;
    m[5] = 0;
    m[6] = 0;
    m[7] = 0;
    m[8] = 1;
    return this;
  }, toString:function() {
    var t = "[";
    for (var i = 0;i < 9;i++) {
      t += this.data[i];
      t += i !== 9 ? ", " : "";
    }
    t += "]";
    return t;
  }, transpose:function() {
    var m = this.data;
    var tmp;
    tmp = m[1];
    m[1] = m[3];
    m[3] = tmp;
    tmp = m[2];
    m[2] = m[6];
    m[6] = tmp;
    tmp = m[5];
    m[5] = m[7];
    m[7] = tmp;
    return this;
  }};
  Object.defineProperty(Mat3, "IDENTITY", {get:function() {
    var identity = new Mat3;
    return function() {
      return identity;
    };
  }()});
  Object.defineProperty(Mat3, "ZERO", {get:function() {
    var zero = new Mat3(0, 0, 0, 0, 0, 0, 0, 0, 0);
    return function() {
      return zero;
    };
  }()});
  return {Mat3:Mat3};
}());
pc.extend(pc, function() {
  var typeNumber = "number";
  var Mat4 = function(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) {
    this.data = new Float32Array(16);
    if (typeof v0 === typeNumber) {
      this.data[0] = v0;
      this.data[1] = v1;
      this.data[2] = v2;
      this.data[3] = v3;
      this.data[4] = v4;
      this.data[5] = v5;
      this.data[6] = v6;
      this.data[7] = v7;
      this.data[8] = v8;
      this.data[9] = v9;
      this.data[10] = v10;
      this.data[11] = v11;
      this.data[12] = v12;
      this.data[13] = v13;
      this.data[14] = v14;
      this.data[15] = v15;
    } else {
      this.setIdentity();
    }
  };
  Mat4.prototype = {add2:function(lhs, rhs) {
    var a = lhs.data, b = rhs.data, r = this.data;
    r[0] = a[0] + b[0];
    r[1] = a[1] + b[1];
    r[2] = a[2] + b[2];
    r[3] = a[3] + b[3];
    r[4] = a[4] + b[4];
    r[5] = a[5] + b[5];
    r[6] = a[6] + b[6];
    r[7] = a[7] + b[7];
    r[8] = a[8] + b[8];
    r[9] = a[9] + b[9];
    r[10] = a[10] + b[10];
    r[11] = a[11] + b[11];
    r[12] = a[12] + b[12];
    r[13] = a[13] + b[13];
    r[14] = a[14] + b[14];
    r[15] = a[15] + b[15];
    return this;
  }, add:function(rhs) {
    return this.add2(this, rhs);
  }, clone:function() {
    return (new pc.Mat4).copy(this);
  }, copy:function(rhs) {
    var src = rhs.data, dst = this.data;
    dst[0] = src[0];
    dst[1] = src[1];
    dst[2] = src[2];
    dst[3] = src[3];
    dst[4] = src[4];
    dst[5] = src[5];
    dst[6] = src[6];
    dst[7] = src[7];
    dst[8] = src[8];
    dst[9] = src[9];
    dst[10] = src[10];
    dst[11] = src[11];
    dst[12] = src[12];
    dst[13] = src[13];
    dst[14] = src[14];
    dst[15] = src[15];
    return this;
  }, equals:function(rhs) {
    var l = this.data, r = rhs.data;
    return l[0] === r[0] && l[1] === r[1] && l[2] === r[2] && l[3] === r[3] && l[4] === r[4] && l[5] === r[5] && l[6] === r[6] && l[7] === r[7] && l[8] === r[8] && l[9] === r[9] && l[10] === r[10] && l[11] === r[11] && l[12] === r[12] && l[13] === r[13] && l[14] === r[14] && l[15] === r[15];
  }, isIdentity:function() {
    var m = this.data;
    return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 0 && m[4] === 0 && m[5] === 1 && m[6] === 0 && m[7] === 0 && m[8] === 0 && m[9] === 0 && m[10] === 1 && m[11] === 0 && m[12] === 0 && m[13] === 0 && m[14] === 0 && m[15] === 1;
  }, mul2:function(lhs, rhs) {
    var a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33, b0, b1, b2, b3, a = lhs.data, b = rhs.data, r = this.data;
    a00 = a[0];
    a01 = a[1];
    a02 = a[2];
    a03 = a[3];
    a10 = a[4];
    a11 = a[5];
    a12 = a[6];
    a13 = a[7];
    a20 = a[8];
    a21 = a[9];
    a22 = a[10];
    a23 = a[11];
    a30 = a[12];
    a31 = a[13];
    a32 = a[14];
    a33 = a[15];
    b0 = b[0];
    b1 = b[1];
    b2 = b[2];
    b3 = b[3];
    r[0] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;
    r[1] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;
    r[2] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;
    r[3] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;
    b0 = b[4];
    b1 = b[5];
    b2 = b[6];
    b3 = b[7];
    r[4] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;
    r[5] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;
    r[6] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;
    r[7] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;
    b0 = b[8];
    b1 = b[9];
    b2 = b[10];
    b3 = b[11];
    r[8] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;
    r[9] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;
    r[10] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;
    r[11] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;
    b0 = b[12];
    b1 = b[13];
    b2 = b[14];
    b3 = b[15];
    r[12] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;
    r[13] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;
    r[14] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;
    r[15] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;
    return this;
  }, mul:function(rhs) {
    return this.mul2(this, rhs);
  }, transformPoint:function(vec, res) {
    var x, y, z, m = this.data, v = vec.data;
    res = res === undefined ? new pc.Vec3 : res;
    x = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + m[12];
    y = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + m[13];
    z = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + m[14];
    return res.set(x, y, z);
  }, transformVector:function(vec, res) {
    var x, y, z, m = this.data, v = vec.data;
    res = res === undefined ? new pc.Vec3 : res;
    x = v[0] * m[0] + v[1] * m[4] + v[2] * m[8];
    y = v[0] * m[1] + v[1] * m[5] + v[2] * m[9];
    z = v[0] * m[2] + v[1] * m[6] + v[2] * m[10];
    return res.set(x, y, z);
  }, setLookAt:function() {
    var x, y, z;
    x = new pc.Vec3;
    y = new pc.Vec3;
    z = new pc.Vec3;
    return function(position, target, up) {
      z.sub2(position, target).normalize();
      y.copy(up).normalize();
      x.cross(y, z).normalize();
      y.cross(z, x);
      var r = this.data;
      r[0] = x.x;
      r[1] = x.y;
      r[2] = x.z;
      r[3] = 0;
      r[4] = y.x;
      r[5] = y.y;
      r[6] = y.z;
      r[7] = 0;
      r[8] = z.x;
      r[9] = z.y;
      r[10] = z.z;
      r[11] = 0;
      r[12] = position.x;
      r[13] = position.y;
      r[14] = position.z;
      r[15] = 1;
      return this;
    };
  }(), setFrustum:function(left, right, bottom, top, znear, zfar) {
    var temp1, temp2, temp3, temp4, r;
    temp1 = 2 * znear;
    temp2 = right - left;
    temp3 = top - bottom;
    temp4 = zfar - znear;
    r = this.data;
    r[0] = temp1 / temp2;
    r[1] = 0;
    r[2] = 0;
    r[3] = 0;
    r[4] = 0;
    r[5] = temp1 / temp3;
    r[6] = 0;
    r[7] = 0;
    r[8] = (right + left) / temp2;
    r[9] = (top + bottom) / temp3;
    r[10] = (-zfar - znear) / temp4;
    r[11] = -1;
    r[12] = 0;
    r[13] = 0;
    r[14] = -temp1 * zfar / temp4;
    r[15] = 0;
    return this;
  }, setPerspective:function(fovy, aspect, znear, zfar, fovIsHorizontal) {
    var xmax, ymax;
    if (!fovIsHorizontal) {
      ymax = znear * Math.tan(fovy * Math.PI / 360);
      xmax = ymax * aspect;
    } else {
      xmax = znear * Math.tan(fovy * Math.PI / 360);
      ymax = xmax / aspect;
    }
    return this.setFrustum(-xmax, xmax, -ymax, ymax, znear, zfar);
  }, setOrtho:function(left, right, bottom, top, near, far) {
    var r = this.data;
    r[0] = 2 / (right - left);
    r[1] = 0;
    r[2] = 0;
    r[3] = 0;
    r[4] = 0;
    r[5] = 2 / (top - bottom);
    r[6] = 0;
    r[7] = 0;
    r[8] = 0;
    r[9] = 0;
    r[10] = -2 / (far - near);
    r[11] = 0;
    r[12] = -(right + left) / (right - left);
    r[13] = -(top + bottom) / (top - bottom);
    r[14] = -(far + near) / (far - near);
    r[15] = 1;
    return this;
  }, setFromAxisAngle:function(axis, angle) {
    var x, y, z, c, s, t, tx, ty, m;
    angle *= pc.math.DEG_TO_RAD;
    x = axis.x;
    y = axis.y;
    z = axis.z;
    c = Math.cos(angle);
    s = Math.sin(angle);
    t = 1 - c;
    tx = t * x;
    ty = t * y;
    m = this.data;
    m[0] = tx * x + c;
    m[1] = tx * y + s * z;
    m[2] = tx * z - s * y;
    m[3] = 0;
    m[4] = tx * y - s * z;
    m[5] = ty * y + c;
    m[6] = ty * z + s * x;
    m[7] = 0;
    m[8] = tx * z + s * y;
    m[9] = ty * z - x * s;
    m[10] = t * z * z + c;
    m[11] = 0;
    m[12] = 0;
    m[13] = 0;
    m[14] = 0;
    m[15] = 1;
    return this;
  }, setTranslate:function(tx, ty, tz) {
    var m = this.data;
    m[0] = 1;
    m[1] = 0;
    m[2] = 0;
    m[3] = 0;
    m[4] = 0;
    m[5] = 1;
    m[6] = 0;
    m[7] = 0;
    m[8] = 0;
    m[9] = 0;
    m[10] = 1;
    m[11] = 0;
    m[12] = tx;
    m[13] = ty;
    m[14] = tz;
    m[15] = 1;
    return this;
  }, setScale:function(sx, sy, sz) {
    var m = this.data;
    m[0] = sx;
    m[1] = 0;
    m[2] = 0;
    m[3] = 0;
    m[4] = 0;
    m[5] = sy;
    m[6] = 0;
    m[7] = 0;
    m[8] = 0;
    m[9] = 0;
    m[10] = sz;
    m[11] = 0;
    m[12] = 0;
    m[13] = 0;
    m[14] = 0;
    m[15] = 1;
    return this;
  }, invert:function() {
    var a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33, b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, invDet, m;
    m = this.data;
    a00 = m[0];
    a01 = m[1];
    a02 = m[2];
    a03 = m[3];
    a10 = m[4];
    a11 = m[5];
    a12 = m[6];
    a13 = m[7];
    a20 = m[8];
    a21 = m[9];
    a22 = m[10];
    a23 = m[11];
    a30 = m[12];
    a31 = m[13];
    a32 = m[14];
    a33 = m[15];
    b00 = a00 * a11 - a01 * a10;
    b01 = a00 * a12 - a02 * a10;
    b02 = a00 * a13 - a03 * a10;
    b03 = a01 * a12 - a02 * a11;
    b04 = a01 * a13 - a03 * a11;
    b05 = a02 * a13 - a03 * a12;
    b06 = a20 * a31 - a21 * a30;
    b07 = a20 * a32 - a22 * a30;
    b08 = a20 * a33 - a23 * a30;
    b09 = a21 * a32 - a22 * a31;
    b10 = a21 * a33 - a23 * a31;
    b11 = a22 * a33 - a23 * a32;
    invDet = 1 / (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06);
    m[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
    m[1] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet;
    m[2] = (a31 * b05 - a32 * b04 + a33 * b03) * invDet;
    m[3] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet;
    m[4] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet;
    m[5] = (a00 * b11 - a02 * b08 + a03 * b07) * invDet;
    m[6] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet;
    m[7] = (a20 * b05 - a22 * b02 + a23 * b01) * invDet;
    m[8] = (a10 * b10 - a11 * b08 + a13 * b06) * invDet;
    m[9] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet;
    m[10] = (a30 * b04 - a31 * b02 + a33 * b00) * invDet;
    m[11] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet;
    m[12] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet;
    m[13] = (a00 * b09 - a01 * b07 + a02 * b06) * invDet;
    m[14] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet;
    m[15] = (a20 * b03 - a21 * b01 + a22 * b00) * invDet;
    return this;
  }, set:function(src) {
    var dst = this.data;
    dst[0] = src[0];
    dst[1] = src[1];
    dst[2] = src[2];
    dst[3] = src[3];
    dst[4] = src[4];
    dst[5] = src[5];
    dst[6] = src[6];
    dst[7] = src[7];
    dst[8] = src[8];
    dst[9] = src[9];
    dst[10] = src[10];
    dst[11] = src[11];
    dst[12] = src[12];
    dst[13] = src[13];
    dst[14] = src[14];
    dst[15] = src[15];
    return this;
  }, setIdentity:function() {
    var m = this.data;
    m[0] = 1;
    m[1] = 0;
    m[2] = 0;
    m[3] = 0;
    m[4] = 0;
    m[5] = 1;
    m[6] = 0;
    m[7] = 0;
    m[8] = 0;
    m[9] = 0;
    m[10] = 1;
    m[11] = 0;
    m[12] = 0;
    m[13] = 0;
    m[14] = 0;
    m[15] = 1;
    return this;
  }, setTRS:function(t, r, s) {
    var tx, ty, tz, qx, qy, qz, qw, sx, sy, sz, x2, y2, z2, xx, xy, xz, yy, yz, zz, wx, wy, wz, m;
    tx = t.x;
    ty = t.y;
    tz = t.z;
    qx = r.x;
    qy = r.y;
    qz = r.z;
    qw = r.w;
    sx = s.x;
    sy = s.y;
    sz = s.z;
    x2 = qx + qx;
    y2 = qy + qy;
    z2 = qz + qz;
    xx = qx * x2;
    xy = qx * y2;
    xz = qx * z2;
    yy = qy * y2;
    yz = qy * z2;
    zz = qz * z2;
    wx = qw * x2;
    wy = qw * y2;
    wz = qw * z2;
    m = this.data;
    m[0] = (1 - (yy + zz)) * sx;
    m[1] = (xy + wz) * sx;
    m[2] = (xz - wy) * sx;
    m[3] = 0;
    m[4] = (xy - wz) * sy;
    m[5] = (1 - (xx + zz)) * sy;
    m[6] = (yz + wx) * sy;
    m[7] = 0;
    m[8] = (xz + wy) * sz;
    m[9] = (yz - wx) * sz;
    m[10] = (1 - (xx + yy)) * sz;
    m[11] = 0;
    m[12] = tx;
    m[13] = ty;
    m[14] = tz;
    m[15] = 1;
    return this;
  }, transpose:function() {
    var tmp, m = this.data;
    tmp = m[1];
    m[1] = m[4];
    m[4] = tmp;
    tmp = m[2];
    m[2] = m[8];
    m[8] = tmp;
    tmp = m[3];
    m[3] = m[12];
    m[12] = tmp;
    tmp = m[6];
    m[6] = m[9];
    m[9] = tmp;
    tmp = m[7];
    m[7] = m[13];
    m[13] = tmp;
    tmp = m[11];
    m[11] = m[14];
    m[14] = tmp;
    return this;
  }, invertTo3x3:function(res) {
    var a11, a21, a31, a12, a22, a32, a13, a23, a33, m, r, det, idet;
    m = this.data;
    r = res.data;
    var m0 = m[0];
    var m1 = m[1];
    var m2 = m[2];
    var m3 = m[3];
    var m4 = m[4];
    var m5 = m[5];
    var m6 = m[6];
    var m7 = m[7];
    var m8 = m[8];
    var m9 = m[9];
    var m10 = m[10];
    a11 = m10 * m5 - m6 * m9;
    a21 = -m10 * m1 + m2 * m9;
    a31 = m6 * m1 - m2 * m5;
    a12 = -m10 * m4 + m6 * m8;
    a22 = m10 * m0 - m2 * m8;
    a32 = -m6 * m0 + m2 * m4;
    a13 = m9 * m4 - m5 * m8;
    a23 = -m9 * m0 + m1 * m8;
    a33 = m5 * m0 - m1 * m4;
    det = m0 * a11 + m1 * a12 + m2 * a13;
    if (det === 0) {
      console.warn("pc.Mat4#invertTo3x3: Matrix not invertible");
      return this;
    }
    idet = 1 / det;
    r[0] = idet * a11;
    r[1] = idet * a21;
    r[2] = idet * a31;
    r[3] = idet * a12;
    r[4] = idet * a22;
    r[5] = idet * a32;
    r[6] = idet * a13;
    r[7] = idet * a23;
    r[8] = idet * a33;
    return this;
  }, getTranslation:function(t) {
    t = t === undefined ? new pc.Vec3 : t;
    return t.set(this.data[12], this.data[13], this.data[14]);
  }, getX:function(x) {
    x = x === undefined ? new pc.Vec3 : x;
    return x.set(this.data[0], this.data[1], this.data[2]);
  }, getY:function(y) {
    y = y === undefined ? new pc.Vec3 : y;
    return y.set(this.data[4], this.data[5], this.data[6]);
  }, getZ:function(z) {
    z = z === undefined ? new pc.Vec3 : z;
    return z.set(this.data[8], this.data[9], this.data[10]);
  }, getScale:function() {
    var x, y, z;
    x = new pc.Vec3;
    y = new pc.Vec3;
    z = new pc.Vec3;
    return function(scale) {
      scale = scale === undefined ? new pc.Vec3 : scale;
      this.getX(x);
      this.getY(y);
      this.getZ(z);
      scale.set(x.length(), y.length(), z.length());
      return scale;
    };
  }(), setFromEulerAngles:function(ex, ey, ez) {
    var s1, c1, s2, c2, s3, c3, m;
    ex *= pc.math.DEG_TO_RAD;
    ey *= pc.math.DEG_TO_RAD;
    ez *= pc.math.DEG_TO_RAD;
    s1 = Math.sin(-ex);
    c1 = Math.cos(-ex);
    s2 = Math.sin(-ey);
    c2 = Math.cos(-ey);
    s3 = Math.sin(-ez);
    c3 = Math.cos(-ez);
    m = this.data;
    m[0] = c2 * c3;
    m[1] = -c2 * s3;
    m[2] = s2;
    m[3] = 0;
    m[4] = c1 * s3 + c3 * s1 * s2;
    m[5] = c1 * c3 - s1 * s2 * s3;
    m[6] = -c2 * s1;
    m[7] = 0;
    m[8] = s1 * s3 - c1 * c3 * s2;
    m[9] = c3 * s1 + c1 * s2 * s3;
    m[10] = c1 * c2;
    m[11] = 0;
    m[12] = 0;
    m[13] = 0;
    m[14] = 0;
    m[15] = 1;
    return this;
  }, getEulerAngles:function() {
    var scale = new pc.Vec3;
    return function(eulers) {
      var x, y, z, sx, sy, sz, m, halfPi;
      eulers = eulers === undefined ? new pc.Vec3 : eulers;
      this.getScale(scale);
      sx = scale.x;
      sy = scale.y;
      sz = scale.z;
      m = this.data;
      y = Math.asin(-m[2] / sx);
      halfPi = Math.PI * .5;
      if (y < halfPi) {
        if (y > -halfPi) {
          x = Math.atan2(m[6] / sy, m[10] / sz);
          z = Math.atan2(m[1] / sx, m[0] / sx);
        } else {
          z = 0;
          x = -Math.atan2(m[4] / sy, m[5] / sy);
        }
      } else {
        z = 0;
        x = Math.atan2(m[4] / sy, m[5] / sy);
      }
      return eulers.set(x, y, z).scale(pc.math.RAD_TO_DEG);
    };
  }(), toString:function() {
    var i, t;
    t = "[";
    for (i = 0;i < 16;i += 1) {
      t += this.data[i];
      t += i !== 15 ? ", " : "";
    }
    t += "]";
    return t;
  }};
  Object.defineProperty(Mat4, "IDENTITY", {get:function() {
    var identity = new Mat4;
    return function() {
      return identity;
    };
  }()});
  Object.defineProperty(Mat4, "ZERO", {get:function() {
    var zero = new Mat4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    return function() {
      return zero;
    };
  }()});
  return {Mat4:Mat4};
}());
pc.extend(pc, function() {
  var Quat = function(x, y, z, w) {
    this.x = x === undefined ? 0 : x;
    this.y = y === undefined ? 0 : y;
    this.z = z === undefined ? 0 : z;
    this.w = w === undefined ? 1 : w;
  };
  Quat.prototype = {clone:function() {
    return new pc.Quat(this.x, this.y, this.z, this.w);
  }, conjugate:function() {
    this.x *= -1;
    this.y *= -1;
    this.z *= -1;
    return this;
  }, copy:function(rhs) {
    this.x = rhs.x;
    this.y = rhs.y;
    this.z = rhs.z;
    this.w = rhs.w;
    return this;
  }, equals:function(that) {
    return this.x === that.x && this.y === that.y && this.z === that.z && this.w === that.w;
  }, getEulerAngles:function(eulers) {
    var x, y, z, qx, qy, qz, qw, a2;
    eulers = eulers === undefined ? new pc.Vec3 : eulers;
    qx = this.x;
    qy = this.y;
    qz = this.z;
    qw = this.w;
    a2 = 2 * (qw * qy - qx * qz);
    if (a2 <= -.99999) {
      x = 2 * Math.atan2(qx, qw);
      y = -Math.PI / 2;
      z = 0;
    } else {
      if (a2 >= .99999) {
        x = 2 * Math.atan2(qx, qw);
        y = Math.PI / 2;
        z = 0;
      } else {
        x = Math.atan2(2 * (qw * qx + qy * qz), 1 - 2 * (qx * qx + qy * qy));
        y = Math.asin(a2);
        z = Math.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qy * qy + qz * qz));
      }
    }
    return eulers.set(x, y, z).scale(pc.math.RAD_TO_DEG);
  }, invert:function() {
    return this.conjugate().normalize();
  }, length:function() {
    var x, y, z, w;
    x = this.x;
    y = this.y;
    z = this.z;
    w = this.w;
    return Math.sqrt(x * x + y * y + z * z + w * w);
  }, lengthSq:function() {
    var x, y, z, w;
    return x * x + y * y + z * z + w * w;
  }, mul:function(rhs) {
    var q1x, q1y, q1z, q1w, q2x, q2y, q2z, q2w;
    q1x = this.x;
    q1y = this.y;
    q1z = this.z;
    q1w = this.w;
    q2x = rhs.x;
    q2y = rhs.y;
    q2z = rhs.z;
    q2w = rhs.w;
    this.x = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y;
    this.y = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z;
    this.z = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x;
    this.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;
    return this;
  }, mul2:function(lhs, rhs) {
    var q1x, q1y, q1z, q1w, q2x, q2y, q2z, q2w;
    q1x = lhs.x;
    q1y = lhs.y;
    q1z = lhs.z;
    q1w = lhs.w;
    q2x = rhs.x;
    q2y = rhs.y;
    q2z = rhs.z;
    q2w = rhs.w;
    this.x = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y;
    this.y = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z;
    this.z = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x;
    this.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;
    return this;
  }, normalize:function() {
    var len = this.length();
    if (len === 0) {
      this.x = this.y = this.z = 0;
      this.w = 1;
    } else {
      len = 1 / len;
      this.x *= len;
      this.y *= len;
      this.z *= len;
      this.w *= len;
    }
    return this;
  }, set:function(x, y, z, w) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.w = w;
    return this;
  }, setFromAxisAngle:function(axis, angle) {
    var sa, ca;
    angle *= .5 * pc.math.DEG_TO_RAD;
    sa = Math.sin(angle);
    ca = Math.cos(angle);
    this.x = sa * axis.x;
    this.y = sa * axis.y;
    this.z = sa * axis.z;
    this.w = ca;
    return this;
  }, setFromEulerAngles:function(ex, ey, ez) {
    var sx, cx, sy, cy, sz, cz, halfToRad;
    halfToRad = .5 * pc.math.DEG_TO_RAD;
    ex *= halfToRad;
    ey *= halfToRad;
    ez *= halfToRad;
    sx = Math.sin(ex);
    cx = Math.cos(ex);
    sy = Math.sin(ey);
    cy = Math.cos(ey);
    sz = Math.sin(ez);
    cz = Math.cos(ez);
    this.x = sx * cy * cz - cx * sy * sz;
    this.y = cx * sy * cz + sx * cy * sz;
    this.z = cx * cy * sz - sx * sy * cz;
    this.w = cx * cy * cz + sx * sy * sz;
    return this;
  }, setFromMat4:function(m) {
    var m00, m01, m02, m10, m11, m12, m20, m21, m22, tr, s, rs, lx, ly, lz;
    m = m.data;
    m00 = m[0];
    m01 = m[1];
    m02 = m[2];
    m10 = m[4];
    m11 = m[5];
    m12 = m[6];
    m20 = m[8];
    m21 = m[9];
    m22 = m[10];
    lx = 1 / Math.sqrt(m00 * m00 + m01 * m01 + m02 * m02);
    ly = 1 / Math.sqrt(m10 * m10 + m11 * m11 + m12 * m12);
    lz = 1 / Math.sqrt(m20 * m20 + m21 * m21 + m22 * m22);
    m00 *= lx;
    m01 *= lx;
    m02 *= lx;
    m10 *= ly;
    m11 *= ly;
    m12 *= ly;
    m20 *= lz;
    m21 *= lz;
    m22 *= lz;
    tr = m00 + m11 + m22;
    if (tr >= 0) {
      s = Math.sqrt(tr + 1);
      this.w = s * .5;
      s = .5 / s;
      this.x = (m12 - m21) * s;
      this.y = (m20 - m02) * s;
      this.z = (m01 - m10) * s;
    } else {
      if (m00 > m11) {
        if (m00 > m22) {
          rs = m00 - (m11 + m22) + 1;
          rs = Math.sqrt(rs);
          this.x = rs * .5;
          rs = .5 / rs;
          this.w = (m12 - m21) * rs;
          this.y = (m01 + m10) * rs;
          this.z = (m02 + m20) * rs;
        } else {
          rs = m22 - (m00 + m11) + 1;
          rs = Math.sqrt(rs);
          this.z = rs * .5;
          rs = .5 / rs;
          this.w = (m01 - m10) * rs;
          this.x = (m20 + m02) * rs;
          this.y = (m21 + m12) * rs;
        }
      } else {
        if (m11 > m22) {
          rs = m11 - (m22 + m00) + 1;
          rs = Math.sqrt(rs);
          this.y = rs * .5;
          rs = .5 / rs;
          this.w = (m20 - m02) * rs;
          this.z = (m12 + m21) * rs;
          this.x = (m10 + m01) * rs;
        } else {
          rs = m22 - (m00 + m11) + 1;
          rs = Math.sqrt(rs);
          this.z = rs * .5;
          rs = .5 / rs;
          this.w = (m01 - m10) * rs;
          this.x = (m20 + m02) * rs;
          this.y = (m21 + m12) * rs;
        }
      }
    }
    return this;
  }, slerp:function(lhs, rhs, alpha) {
    var lx, ly, lz, lw, rx, ry, rz, rw;
    lx = lhs.x;
    ly = lhs.y;
    lz = lhs.z;
    lw = lhs.w;
    rx = rhs.x;
    ry = rhs.y;
    rz = rhs.z;
    rw = rhs.w;
    var cosHalfTheta = lw * rw + lx * rx + ly * ry + lz * rz;
    if (cosHalfTheta < 0) {
      rw = -rw;
      rx = -rx;
      ry = -ry;
      rz = -rz;
      cosHalfTheta = -cosHalfTheta;
    }
    if (Math.abs(cosHalfTheta) >= 1) {
      this.w = lw;
      this.x = lx;
      this.y = ly;
      this.z = lz;
      return this;
    }
    var halfTheta = Math.acos(cosHalfTheta);
    var sinHalfTheta = Math.sqrt(1 - cosHalfTheta * cosHalfTheta);
    if (Math.abs(sinHalfTheta) < .001) {
      this.w = lw * .5 + rw * .5;
      this.x = lx * .5 + rx * .5;
      this.y = ly * .5 + ry * .5;
      this.z = lz * .5 + rz * .5;
      return this;
    }
    var ratioA = Math.sin((1 - alpha) * halfTheta) / sinHalfTheta;
    var ratioB = Math.sin(alpha * halfTheta) / sinHalfTheta;
    this.w = lw * ratioA + rw * ratioB;
    this.x = lx * ratioA + rx * ratioB;
    this.y = ly * ratioA + ry * ratioB;
    this.z = lz * ratioA + rz * ratioB;
    return this;
  }, transformVector:function(vec, res) {
    if (res === undefined) {
      res = new pc.Vec3;
    }
    var x = vec.x, y = vec.y, z = vec.z;
    var qx = this.x, qy = this.y, qz = this.z, qw = this.w;
    var ix = qw * x + qy * z - qz * y;
    var iy = qw * y + qz * x - qx * z;
    var iz = qw * z + qx * y - qy * x;
    var iw = -qx * x - qy * y - qz * z;
    res.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
    res.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
    res.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
    return res;
  }, toString:function() {
    return "[" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + "]";
  }};
  Object.defineProperty(Quat, "IDENTITY", {get:function() {
    var identity = new Quat;
    return function() {
      return identity;
    };
  }()});
  Object.defineProperty(Quat, "ZERO", {get:function() {
    var zero = new Quat(0, 0, 0, 0);
    return function() {
      return zero;
    };
  }()});
  return {Quat:Quat};
}());
pc.extend(pc, function() {
  var CURVE_LINEAR = 0;
  var CURVE_SMOOTHSTEP = 1;
  var CURVE_CATMULL = 2;
  var CURVE_CARDINAL = 3;
  var Curve = function(data) {
    this.keys = [];
    this.type = CURVE_SMOOTHSTEP;
    this.tension = .5;
    if (data) {
      for (var i = 0;i < data.length - 1;i += 2) {
        this.keys.push([data[i], data[i + 1]]);
      }
    }
    this.sort();
  };
  Curve.prototype = {add:function(time, value) {
    var keys = this.keys;
    var len = keys.length;
    var i = 0;
    for (;i < len;i++) {
      if (keys[i][0] > time) {
        break;
      }
    }
    var key = [time, value];
    this.keys.splice(i, 0, key);
    return key;
  }, get:function(index) {
    return this.keys[index];
  }, sort:function() {
    this.keys.sort(function(a, b) {
      return a[0] - b[0];
    });
  }, value:function(time) {
    var keys = this.keys;
    if (!keys.length) {
      return 0;
    }
    if (time < keys[0][0]) {
      return keys[0][1];
    } else {
      if (time > keys[keys.length - 1][0]) {
        return keys[keys.length - 1][1];
      }
    }
    var leftTime = 0;
    var leftValue = keys.length ? keys[0][1] : 0;
    var rightTime = 1;
    var rightValue = 0;
    for (var i = 0, len = keys.length;i < len;i++) {
      if (keys[i][0] === time) {
        return keys[i][1];
      }
      rightValue = keys[i][1];
      if (time < keys[i][0]) {
        rightTime = keys[i][0];
        break;
      }
      leftTime = keys[i][0];
      leftValue = keys[i][1];
    }
    var div = rightTime - leftTime;
    var interpolation = div === 0 ? 0 : (time - leftTime) / div;
    if (this.type === CURVE_SMOOTHSTEP) {
      interpolation *= interpolation * (3 - 2 * interpolation);
    } else {
      if (this.type === CURVE_CATMULL || this.type === CURVE_CARDINAL) {
        var p1 = leftValue;
        var p2 = rightValue;
        var p0 = p1 + (p1 - p2);
        var p3 = p2 + (p2 - p1);
        var dt1 = rightTime - leftTime;
        var dt0 = dt1;
        var dt2 = dt1;
        if (i > 0) {
          i = i - 1;
        }
        if (i > 0) {
          p0 = keys[i - 1][1];
          dt0 = keys[i][0] - keys[i - 1][0];
        }
        if (keys.length > i + 1) {
          dt1 = keys[i + 1][0] - keys[i][0];
        }
        if (keys.length > i + 2) {
          dt2 = keys[i + 2][0] - keys[i + 1][0];
          p3 = keys[i + 2][1];
        }
        p0 = p1 + (p0 - p1) * dt1 / dt0;
        p3 = p2 + (p3 - p2) * dt1 / dt2;
        if (this.type === CURVE_CATMULL) {
          return this._interpolateCatmullRom(p0, p1, p2, p3, interpolation);
        } else {
          return this._interpolateCardinal(p0, p1, p2, p3, interpolation, this.tension);
        }
      }
    }
    return pc.math.lerp(leftValue, rightValue, interpolation);
  }, _interpolateHermite:function(p0, p1, t0, t1, s) {
    var s2 = s * s;
    var s3 = s * s * s;
    var h0 = 2 * s3 - 3 * s2 + 1;
    var h1 = -2 * s3 + 3 * s2;
    var h2 = s3 - 2 * s2 + s;
    var h3 = s3 - s2;
    return p0 * h0 + p1 * h1 + t0 * h2 + t1 * h3;
  }, _interpolateCardinal:function(p0, p1, p2, p3, s, t) {
    var t0 = t * (p2 - p0);
    var t1 = t * (p3 - p1);
    return this._interpolateHermite(p1, p2, t0, t1, s);
  }, _interpolateCatmullRom:function(p0, p1, p2, p3, s) {
    return this._interpolateCardinal(p0, p1, p2, p3, s, .5);
  }, closest:function(time) {
    var keys = this.keys;
    var length = keys.length;
    var min = 2;
    var result = null;
    for (var i = 0;i < length;i++) {
      var diff = Math.abs(time - keys[i][0]);
      if (min >= diff) {
        min = diff;
        result = keys[i];
      } else {
        break;
      }
    }
    return result;
  }, clone:function() {
    var result = new pc.Curve;
    result.keys = pc.extend(result.keys, this.keys);
    result.type = this.type;
    return result;
  }, quantize:function(precision) {
    precision = Math.max(precision, 2);
    var values = new Float32Array(precision);
    var step = 1 / (precision - 1);
    for (var i = 0;i < precision;i++) {
      var value = this.value(step * i);
      values[i] = value;
    }
    return values;
  }};
  Object.defineProperty(Curve.prototype, "length", {get:function() {
    return this.keys.length;
  }});
  return {Curve:Curve, CURVE_LINEAR:CURVE_LINEAR, CURVE_SMOOTHSTEP:CURVE_SMOOTHSTEP, CURVE_CATMULL:CURVE_CATMULL, CURVE_CARDINAL:CURVE_CARDINAL};
}());
pc.extend(pc, function() {
  var CurveSet = function() {
    var i;
    this.curves = [];
    this._type = pc.CURVE_SMOOTHSTEP;
    if (arguments.length > 1) {
      for (i = 0;i < arguments.length;i++) {
        this.curves.push(new pc.Curve(arguments[i]));
      }
    } else {
      if (arguments.length === 0) {
        this.curves.push(new pc.Curve);
      } else {
        var arg = arguments[0];
        if (pc.type(arg) === "number") {
          for (i = 0;i < arg;i++) {
            this.curves.push(new pc.Curve);
          }
        } else {
          for (i = 0;i < arg.length;i++) {
            this.curves.push(new pc.Curve(arg[i]));
          }
        }
      }
    }
  };
  CurveSet.prototype = {get:function(index) {
    return this.curves[index];
  }, value:function(time, result) {
    var length = this.curves.length;
    result = result || [];
    result.length = length;
    for (var i = 0;i < length;i++) {
      result[i] = this.curves[i].value(time);
    }
    return result;
  }, clone:function() {
    var result = new pc.CurveSet;
    result.curves = [];
    for (var i = 0;i < this.curves.length;i++) {
      result.curves.push(this.curves[i].clone());
    }
    result._type = this._type;
    return result;
  }, quantize:function(precision) {
    precision = Math.max(precision, 2);
    var numCurves = this.curves.length;
    var values = new Float32Array(precision * numCurves);
    var step = 1 / (precision - 1);
    var temp = [];
    for (var i = 0;i < precision;i++) {
      var value = this.value(step * i, temp);
      if (numCurves == 1) {
        values[i] = value[0];
      } else {
        for (var j = 0;j < numCurves;j++) {
          values[i * numCurves + j] = value[j];
        }
      }
    }
    return values;
  }};
  Object.defineProperty(CurveSet.prototype, "length", {get:function() {
    return this.curves.length;
  }});
  Object.defineProperty(CurveSet.prototype, "type", {get:function() {
    return this._type;
  }, set:function(value) {
    this._type = value;
    for (var i = 0;i < this.curves.length;i++) {
      this.curves[i].type = value;
    }
  }});
  return {CurveSet:CurveSet};
}());
pc.extend(pc, function() {
  var tmpVecA = new pc.Vec3;
  var tmpVecB = new pc.Vec3;
  var tmpVecC = new pc.Vec3;
  var tmpVecD = new pc.Vec3;
  var tmpVecE = new pc.Vec3;
  var tmpVecF = new pc.Vec3;
  var BoundingBox = function BoundingBox(center, halfExtents) {
    this.center = center || new pc.Vec3(0, 0, 0);
    this.halfExtents = halfExtents || new pc.Vec3(.5, .5, .5);
    this._min = new pc.Vec3;
    this._max = new pc.Vec3;
  };
  BoundingBox.prototype = {add:function(other) {
    var tc = this.center.data;
    var tcx = tc[0];
    var tcy = tc[1];
    var tcz = tc[2];
    var th = this.halfExtents.data;
    var thx = th[0];
    var thy = th[1];
    var thz = th[2];
    var tminx = tcx - thx;
    var tmaxx = tcx + thx;
    var tminy = tcy - thy;
    var tmaxy = tcy + thy;
    var tminz = tcz - thz;
    var tmaxz = tcz + thz;
    var oc = other.center.data;
    var ocx = oc[0];
    var ocy = oc[1];
    var ocz = oc[2];
    var oh = other.halfExtents.data;
    var ohx = oh[0];
    var ohy = oh[1];
    var ohz = oh[2];
    var ominx = ocx - ohx;
    var omaxx = ocx + ohx;
    var ominy = ocy - ohy;
    var omaxy = ocy + ohy;
    var ominz = ocz - ohz;
    var omaxz = ocz + ohz;
    if (ominx < tminx) {
      tminx = ominx;
    }
    if (omaxx > tmaxx) {
      tmaxx = omaxx;
    }
    if (ominy < tminy) {
      tminy = ominy;
    }
    if (omaxy > tmaxy) {
      tmaxy = omaxy;
    }
    if (ominz < tminz) {
      tminz = ominz;
    }
    if (omaxz > tmaxz) {
      tmaxz = omaxz;
    }
    tc[0] = (tminx + tmaxx) * .5;
    tc[1] = (tminy + tmaxy) * .5;
    tc[2] = (tminz + tmaxz) * .5;
    th[0] = (tmaxx - tminx) * .5;
    th[1] = (tmaxy - tminy) * .5;
    th[2] = (tmaxz - tminz) * .5;
  }, copy:function(src) {
    this.center.copy(src.center);
    this.halfExtents.copy(src.halfExtents);
    this.type = src.type;
  }, clone:function() {
    return new pc.BoundingBox(this.center.clone(), this.halfExtents.clone());
  }, intersects:function(other) {
    var aMax = this.getMax();
    var aMin = this.getMin();
    var bMax = other.getMax();
    var bMin = other.getMin();
    return aMin.x <= bMax.x && aMax.x >= bMin.x && aMin.y <= bMax.y && aMax.y >= bMin.y && aMin.z <= bMax.z && aMax.z >= bMin.z;
  }, intersectsRay:function(ray) {
    var diff = tmpVecA;
    var cross = tmpVecB;
    var prod = tmpVecC;
    var absDiff = tmpVecD;
    var absDir = tmpVecE;
    var rayDir = tmpVecF.copy(ray.direction).normalize();
    var i;
    diff.sub2(ray.origin, this.center);
    absDiff.set(Math.abs(diff.x), Math.abs(diff.y), Math.abs(diff.z));
    prod.mul2(diff, rayDir);
    if (absDiff.x > this.halfExtents.x && prod.x >= 0) {
      return false;
    }
    if (absDiff.y > this.halfExtents.y && prod.y >= 0) {
      return false;
    }
    if (absDiff.z > this.halfExtents.z && prod.z >= 0) {
      return false;
    }
    absDir.set(Math.abs(rayDir.x), Math.abs(rayDir.y), Math.abs(rayDir.z));
    cross.cross(rayDir, diff);
    cross.set(Math.abs(cross.x), Math.abs(cross.y), Math.abs(cross.z));
    if (cross.x > this.halfExtents.y * absDir.z + this.halfExtents.z * absDir.y) {
      return false;
    }
    if (cross.y > this.halfExtents.x * absDir.z + this.halfExtents.z * absDir.x) {
      return false;
    }
    if (cross.z > this.halfExtents.x * absDir.y + this.halfExtents.y * absDir.x) {
      return false;
    }
    return true;
  }, setMinMax:function(min, max) {
    this.center.add2(max, min).scale(.5);
    this.halfExtents.sub2(max, min).scale(.5);
  }, getMin:function() {
    return this._min.copy(this.center).sub(this.halfExtents);
  }, getMax:function() {
    return this._max.copy(this.center).add(this.halfExtents);
  }, containsPoint:function(point) {
    var min = this.getMin();
    var max = this.getMax();
    var i;
    for (i = 0;i < 3;++i) {
      if (point.data[i] < min.data[i] || point.data[i] > max.data[i]) {
        return false;
      }
    }
    return true;
  }, setFromTransformedAabb:function(aabb, m) {
    var bc = this.center;
    var br = this.halfExtents;
    var ac = aabb.center.data;
    var ar = aabb.halfExtents.data;
    m = m.data;
    var mx0 = m[0];
    var mx1 = m[4];
    var mx2 = m[8];
    var my0 = m[1];
    var my1 = m[5];
    var my2 = m[9];
    var mz0 = m[2];
    var mz1 = m[6];
    var mz2 = m[10];
    var mx0a = Math.abs(mx0);
    var mx1a = Math.abs(mx1);
    var mx2a = Math.abs(mx2);
    var my0a = Math.abs(my0);
    var my1a = Math.abs(my1);
    var my2a = Math.abs(my2);
    var mz0a = Math.abs(mz0);
    var mz1a = Math.abs(mz1);
    var mz2a = Math.abs(mz2);
    bc.set(m[12] + mx0 * ac[0] + mx1 * ac[1] + mx2 * ac[2], m[13] + my0 * ac[0] + my1 * ac[1] + my2 * ac[2], m[14] + mz0 * ac[0] + mz1 * ac[1] + mz2 * ac[2]);
    br.set(mx0a * ar[0] + mx1a * ar[1] + mx2a * ar[2], my0a * ar[0] + my1a * ar[1] + my2a * ar[2], mz0a * ar[0] + mz1a * ar[1] + mz2a * ar[2]);
  }, compute:function(vertices) {
    var min = tmpVecA.set(vertices[0], vertices[1], vertices[2]);
    var max = tmpVecB.set(vertices[0], vertices[1], vertices[2]);
    var numVerts = vertices.length / 3;
    for (var i = 1;i < numVerts;i++) {
      var x = vertices[i * 3 + 0];
      var y = vertices[i * 3 + 1];
      var z = vertices[i * 3 + 2];
      if (x < min.x) {
        min.x = x;
      }
      if (y < min.y) {
        min.y = y;
      }
      if (z < min.z) {
        min.z = z;
      }
      if (x > max.x) {
        max.x = x;
      }
      if (y > max.y) {
        max.y = y;
      }
      if (z > max.z) {
        max.z = z;
      }
    }
    this.setMinMax(min, max);
  }};
  return {BoundingBox:BoundingBox};
}());
pc.extend(pc, function() {
  var tmpVecA = new pc.Vec3;
  var tmpVecB = new pc.Vec3;
  var tmpVecC = new pc.Vec3;
  var tmpVecD = new pc.Vec3;
  function BoundingSphere(center, radius) {
    this.center = center || new pc.Vec3(0, 0, 0);
    this.radius = radius === undefined ? .5 : radius;
  }
  BoundingSphere.prototype = {containsPoint:function(point) {
    var lenSq = tmpVecA.sub2(point, this.center).lengthSq();
    var r = this.radius;
    return lenSq < r * r;
  }, compute:function(vertices) {
    var i;
    var numVerts = vertices.length / 3;
    var vertex = tmpVecA;
    var avgVertex = tmpVecB;
    var sum = tmpVecC;
    for (i = 0;i < numVerts;i++) {
      vertex.set(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]);
      sum.addSelf(vertex);
      if (i % 100 === 0) {
        sum.scale(1 / numVerts);
        avgVertex.add(sum);
        sum.set(0, 0, 0);
      }
    }
    sum.scale(1 / numVerts);
    avgVertex.add(sum);
    this.center.copy(avgVertex);
    var maxDistSq = 0;
    var centerToVert = tmpVecD;
    for (i = 0;i < numVerts;i++) {
      vertex.set(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]);
      centerToVert.sub2(vertex, this.center);
      maxDistSq = Math.max(centerToVert.lengthSq(), maxDistSq);
    }
    this.radius = Math.sqrt(maxDistSq);
  }, intersectRay:function(ray) {
    var m = tmpVecA.copy(ray.origin).sub(this.center);
    var b = m.dot(tmpVecB.copy(ray.direction).normalize());
    var c = m.dot(m) - this.radius * this.radius;
    if (c > 0 && b > 0) {
      return null;
    }
    var discr = b * b - c;
    if (discr < 0) {
      return null;
    }
    var t = Math.abs(-b - Math.sqrt(discr));
    return ray.direction.clone().scale(t).add(ray.origin);
  }};
  return {BoundingSphere:BoundingSphere};
}());
pc.extend(pc, function() {
  var viewProj = new pc.Mat4;
  var Frustum = function Frustum(projectionMatrix, viewMatrix) {
    projectionMatrix = projectionMatrix || (new pc.Mat4).setPerspective(90, 16 / 9, .1, 1E3);
    viewMatrix = viewMatrix || new pc.Mat4;
    this.planes = [];
    for (var i = 0;i < 6;i++) {
      this.planes[i] = [];
    }
    this.update(projectionMatrix, viewMatrix);
  };
  Frustum.prototype = {update:function(projectionMatrix, viewMatrix) {
    viewProj.mul2(projectionMatrix, viewMatrix);
    var vpm = viewProj.data;
    this.planes[0][0] = vpm[3] - vpm[0];
    this.planes[0][1] = vpm[7] - vpm[4];
    this.planes[0][2] = vpm[11] - vpm[8];
    this.planes[0][3] = vpm[15] - vpm[12];
    var t = Math.sqrt(this.planes[0][0] * this.planes[0][0] + this.planes[0][1] * this.planes[0][1] + this.planes[0][2] * this.planes[0][2]);
    this.planes[0][0] /= t;
    this.planes[0][1] /= t;
    this.planes[0][2] /= t;
    this.planes[0][3] /= t;
    this.planes[1][0] = vpm[3] + vpm[0];
    this.planes[1][1] = vpm[7] + vpm[4];
    this.planes[1][2] = vpm[11] + vpm[8];
    this.planes[1][3] = vpm[15] + vpm[12];
    t = Math.sqrt(this.planes[1][0] * this.planes[1][0] + this.planes[1][1] * this.planes[1][1] + this.planes[1][2] * this.planes[1][2]);
    this.planes[1][0] /= t;
    this.planes[1][1] /= t;
    this.planes[1][2] /= t;
    this.planes[1][3] /= t;
    this.planes[2][0] = vpm[3] + vpm[1];
    this.planes[2][1] = vpm[7] + vpm[5];
    this.planes[2][2] = vpm[11] + vpm[9];
    this.planes[2][3] = vpm[15] + vpm[13];
    t = Math.sqrt(this.planes[2][0] * this.planes[2][0] + this.planes[2][1] * this.planes[2][1] + this.planes[2][2] * this.planes[2][2]);
    this.planes[2][0] /= t;
    this.planes[2][1] /= t;
    this.planes[2][2] /= t;
    this.planes[2][3] /= t;
    this.planes[3][0] = vpm[3] - vpm[1];
    this.planes[3][1] = vpm[7] - vpm[5];
    this.planes[3][2] = vpm[11] - vpm[9];
    this.planes[3][3] = vpm[15] - vpm[13];
    t = Math.sqrt(this.planes[3][0] * this.planes[3][0] + this.planes[3][1] * this.planes[3][1] + this.planes[3][2] * this.planes[3][2]);
    this.planes[3][0] /= t;
    this.planes[3][1] /= t;
    this.planes[3][2] /= t;
    this.planes[3][3] /= t;
    this.planes[4][0] = vpm[3] - vpm[2];
    this.planes[4][1] = vpm[7] - vpm[6];
    this.planes[4][2] = vpm[11] - vpm[10];
    this.planes[4][3] = vpm[15] - vpm[14];
    t = Math.sqrt(this.planes[4][0] * this.planes[4][0] + this.planes[4][1] * this.planes[4][1] + this.planes[4][2] * this.planes[4][2]);
    this.planes[4][0] /= t;
    this.planes[4][1] /= t;
    this.planes[4][2] /= t;
    this.planes[4][3] /= t;
    this.planes[5][0] = vpm[3] + vpm[2];
    this.planes[5][1] = vpm[7] + vpm[6];
    this.planes[5][2] = vpm[11] + vpm[10];
    this.planes[5][3] = vpm[15] + vpm[14];
    t = Math.sqrt(this.planes[5][0] * this.planes[5][0] + this.planes[5][1] * this.planes[5][1] + this.planes[5][2] * this.planes[5][2]);
    this.planes[5][0] /= t;
    this.planes[5][1] /= t;
    this.planes[5][2] /= t;
    this.planes[5][3] /= t;
  }, containsPoint:function(point) {
    for (var p = 0;p < 6;p++) {
      if (this.planes[p][0] * point.x + this.planes[p][1] * point.y + this.planes[p][2] * point.z + this.planes[p][3] <= 0) {
        return false;
      }
    }
    return true;
  }, containsSphere:function(sphere) {
    var c = 0;
    var d;
    var p;
    var sr = sphere.radius;
    var sc = sphere.center.data;
    var scx = sc[0];
    var scy = sc[1];
    var scz = sc[2];
    var planes = this.planes;
    var plane;
    for (p = 0;p < 6;p++) {
      plane = planes[p];
      d = plane[0] * scx + plane[1] * scy + plane[2] * scz + plane[3];
      if (d <= -sr) {
        return 0;
      }
      if (d > sr) {
        c++;
      }
    }
    return c === 6 ? 2 : 1;
  }};
  return {Frustum:Frustum};
}());
pc.extend(pc, function() {
  var Plane = function Plane(point, normal) {
    this.normal = normal || new pc.Vec3(0, 0, 1);
    this.point = point || new pc.Vec3(0, 0, 0);
    this.d = -this.normal.dot(this.point);
  };
  Plane.prototype = {distance:function(pos) {
    return this.normal.dot(pos) + this.d;
  }, intersect:function(p0, p1) {
    var d0 = this.distance(p0);
    var d1 = this.distance(p1);
    return d0 / (d0 - d1);
  }, intersectPosition:function(p0, p1) {
    var t = this.intersect(p0, p1);
    var r = new pc.Vec3;
    r.lerp(p0, p1, t);
    return r;
  }};
  return {Plane:Plane};
}());
pc.extend(pc, function() {
  var Ray = function Ray(origin, direction) {
    this.origin = origin || new pc.Vec3(0, 0, 0);
    this.direction = direction || new pc.Vec3(0, 0, -1);
  };
  return {Ray:Ray};
}());
(function() {
  var enums = {ADDRESS_REPEAT:0, ADDRESS_CLAMP_TO_EDGE:1, ADDRESS_MIRRORED_REPEAT:2, BLENDMODE_ZERO:0, BLENDMODE_ONE:1, BLENDMODE_SRC_COLOR:2, BLENDMODE_ONE_MINUS_SRC_COLOR:3, BLENDMODE_DST_COLOR:4, BLENDMODE_ONE_MINUS_DST_COLOR:5, BLENDMODE_SRC_ALPHA:6, BLENDMODE_SRC_ALPHA_SATURATE:7, BLENDMODE_ONE_MINUS_SRC_ALPHA:8, BLENDMODE_DST_ALPHA:9, BLENDMODE_ONE_MINUS_DST_ALPHA:10, BLENDEQUATION_ADD:0, BLENDEQUATION_SUBTRACT:1, BLENDEQUATION_REVERSE_SUBTRACT:2, BUFFER_STATIC:0, BUFFER_DYNAMIC:1, BUFFER_STREAM:2, 
  CLEARFLAG_COLOR:1, CLEARFLAG_DEPTH:2, CLEARFLAG_STENCIL:4, CUBEFACE_POSX:0, CUBEFACE_NEGX:1, CUBEFACE_POSY:2, CUBEFACE_NEGY:3, CUBEFACE_POSZ:4, CUBEFACE_NEGZ:5, CULLFACE_NONE:0, CULLFACE_BACK:1, CULLFACE_FRONT:2, CULLFACE_FRONTANDBACK:3, ELEMENTTYPE_INT8:0, ELEMENTTYPE_UINT8:1, ELEMENTTYPE_INT16:2, ELEMENTTYPE_UINT16:3, ELEMENTTYPE_INT32:4, ELEMENTTYPE_UINT32:5, ELEMENTTYPE_FLOAT32:6, FILTER_NEAREST:0, FILTER_LINEAR:1, FILTER_NEAREST_MIPMAP_NEAREST:2, FILTER_NEAREST_MIPMAP_LINEAR:3, FILTER_LINEAR_MIPMAP_NEAREST:4, 
  FILTER_LINEAR_MIPMAP_LINEAR:5, FUNC_NEVER:0, FUNC_LESS:1, FUNC_EQUAL:2, FUNC_LESSEQUAL:3, FUNC_GREATER:4, FUNC_NOTEQUAL:5, FUNC_GREATEREQUAL:6, FUNC_ALWAYS:7, INDEXFORMAT_UINT8:0, INDEXFORMAT_UINT16:1, INDEXFORMAT_UINT32:2, PIXELFORMAT_A8:0, PIXELFORMAT_L8:1, PIXELFORMAT_L8_A8:2, PIXELFORMAT_R5_G6_B5:3, PIXELFORMAT_R5_G5_B5_A1:4, PIXELFORMAT_R4_G4_B4_A4:5, PIXELFORMAT_R8_G8_B8:6, PIXELFORMAT_R8_G8_B8_A8:7, PIXELFORMAT_DXT1:8, PIXELFORMAT_DXT3:9, PIXELFORMAT_DXT5:10, PIXELFORMAT_RGB16F:11, PIXELFORMAT_RGBA16F:12, 
  PIXELFORMAT_RGB32F:13, PIXELFORMAT_RGBA32F:14, PIXELFORMAT_ETC1:15, PIXELFORMAT_PVRTC_2BPP_RGB_1:16, PIXELFORMAT_PVRTC_2BPP_RGBA_1:17, PIXELFORMAT_PVRTC_4BPP_RGB_1:18, PIXELFORMAT_PVRTC_4BPP_RGBA_1:19, PRIMITIVE_POINTS:0, PRIMITIVE_LINES:1, PRIMITIVE_LINELOOP:2, PRIMITIVE_LINESTRIP:3, PRIMITIVE_TRIANGLES:4, PRIMITIVE_TRISTRIP:5, PRIMITIVE_TRIFAN:6, SEMANTIC_POSITION:"POSITION", SEMANTIC_NORMAL:"NORMAL", SEMANTIC_TANGENT:"TANGENT", SEMANTIC_BLENDWEIGHT:"BLENDWEIGHT", SEMANTIC_BLENDINDICES:"BLENDINDICES", 
  SEMANTIC_COLOR:"COLOR", SEMANTIC_TEXCOORD0:"TEXCOORD0", SEMANTIC_TEXCOORD1:"TEXCOORD1", SEMANTIC_TEXCOORD2:"TEXCOORD2", SEMANTIC_TEXCOORD3:"TEXCOORD3", SEMANTIC_TEXCOORD4:"TEXCOORD4", SEMANTIC_TEXCOORD5:"TEXCOORD5", SEMANTIC_TEXCOORD6:"TEXCOORD6", SEMANTIC_TEXCOORD7:"TEXCOORD7", SEMANTIC_ATTR0:"ATTR0", SEMANTIC_ATTR1:"ATTR1", SEMANTIC_ATTR2:"ATTR2", SEMANTIC_ATTR3:"ATTR3", SEMANTIC_ATTR4:"ATTR4", SEMANTIC_ATTR5:"ATTR5", SEMANTIC_ATTR6:"ATTR6", SEMANTIC_ATTR7:"ATTR7", SEMANTIC_ATTR8:"ATTR8", SEMANTIC_ATTR9:"ATTR9", 
  SEMANTIC_ATTR10:"ATTR10", SEMANTIC_ATTR11:"ATTR11", SEMANTIC_ATTR12:"ATTR12", SEMANTIC_ATTR13:"ATTR13", SEMANTIC_ATTR14:"ATTR14", SEMANTIC_ATTR15:"ATTR15", SHADERTAG_MATERIAL:1, STENCILOP_KEEP:0, STENCILOP_ZERO:1, STENCILOP_REPLACE:2, STENCILOP_INCREMENT:3, STENCILOP_INCREMENTWRAP:4, STENCILOP_DECREMENT:5, STENCILOP_DECREMENTWRAP:6, STENCILOP_INVERT:7, TEXTURELOCK_READ:1, TEXTURELOCK_WRITE:2, TEXHINT_NONE:0, TEXHINT_SHADOWMAP:1, TEXHINT_ASSET:2, TEXHINT_LIGHTMAP:3, UNIFORMTYPE_BOOL:0, UNIFORMTYPE_INT:1, 
  UNIFORMTYPE_FLOAT:2, UNIFORMTYPE_VEC2:3, UNIFORMTYPE_VEC3:4, UNIFORMTYPE_VEC4:5, UNIFORMTYPE_IVEC2:6, UNIFORMTYPE_IVEC3:7, UNIFORMTYPE_IVEC4:8, UNIFORMTYPE_BVEC2:9, UNIFORMTYPE_BVEC3:10, UNIFORMTYPE_BVEC4:11, UNIFORMTYPE_MAT2:12, UNIFORMTYPE_MAT3:13, UNIFORMTYPE_MAT4:14, UNIFORMTYPE_TEXTURE2D:15, UNIFORMTYPE_TEXTURECUBE:16, UNIFORMTYPE_FLOATARRAY:17};
  pc.extend(pc, enums);
  pc.gfx = {};
  pc.extend(pc.gfx, enums);
})();
pc.extend(pc, function() {
  var ScopeId = function(name) {
    this.name = name;
    this.value = null;
    this.versionObject = new pc.VersionedObject;
  };
  ScopeId.prototype = {setValue:function(value) {
    this.value = value;
    this.versionObject.increment();
  }, getValue:function(value) {
    return this.value;
  }};
  return {ScopeId:ScopeId};
}());
pc.extend(pc, function() {
  var ScopeSpace = function(name) {
    this.name = name;
    this.variables = {};
    this.namespaces = {};
  };
  ScopeSpace.prototype = {resolve:function(name) {
    if (this.variables.hasOwnProperty(name) === false) {
      this.variables[name] = new pc.ScopeId(name);
    }
    return this.variables[name];
  }, getSubSpace:function(name) {
    if (this.namespaces.hasOwnProperty(name) === false) {
      this.namespaces[name] = new pc.ScopeSpace(name);
      logDEBUG("Added ScopeSpace: " + name);
    }
    return this.namespaces[name];
  }};
  return {ScopeSpace:ScopeSpace};
}());
pc.extend(pc, function() {
  var Version = function() {
    this.globalId = 0;
    this.revision = 0;
  };
  Version.prototype = {equals:function(other) {
    return this.globalId === other.globalId && this.revision === other.revision;
  }, notequals:function(other) {
    return this.globalId !== other.globalId || this.revision !== other.revision;
  }, copy:function(other) {
    this.globalId = other.globalId;
    this.revision = other.revision;
  }, reset:function() {
    this.globalId = 0;
    this.revision = 0;
  }};
  return {Version:Version};
}());
pc.extend(pc, function() {
  var idCounter = 0;
  var VersionedObject = function() {
    idCounter++;
    this.version = new pc.Version;
    this.version.globalId = idCounter;
  };
  VersionedObject.prototype = {increment:function() {
    this.version.revision++;
  }};
  return {VersionedObject:VersionedObject};
}());
pc.extend(pc, function() {
  function VertexIteratorSetter(buffer, vertexElement) {
    this.index = 0;
    switch(vertexElement.dataType) {
      case pc.ELEMENTTYPE_INT8:
        this.array = new Int8Array(buffer, vertexElement.offset);
        break;
      case pc.ELEMENTTYPE_UINT8:
        this.array = new Uint8Array(buffer, vertexElement.offset);
        break;
      case pc.ELEMENTTYPE_INT16:
        this.array = new Int16Array(buffer, vertexElement.offset);
        break;
      case pc.ELEMENTTYPE_UINT16:
        this.array = new Uint16Array(buffer, vertexElement.offset);
        break;
      case pc.ELEMENTTYPE_INT32:
        this.array = new Int32Array(buffer, vertexElement.offset);
        break;
      case pc.ELEMENTTYPE_UINT32:
        this.array = new Uint32Array(buffer, vertexElement.offset);
        break;
      case pc.ELEMENTTYPE_FLOAT32:
        this.array = new Float32Array(buffer, vertexElement.offset);
        break;
    }
    switch(vertexElement.numComponents) {
      case 1:
        this.set = VertexIteratorSetter_set1;
        break;
      case 2:
        this.set = VertexIteratorSetter_set2;
        break;
      case 3:
        this.set = VertexIteratorSetter_set3;
        break;
      case 4:
        this.set = VertexIteratorSetter_set4;
        break;
    }
  }
  function VertexIteratorSetter_set1(a) {
    this.array[this.index] = a;
  }
  function VertexIteratorSetter_set2(a, b) {
    this.array[this.index] = a;
    this.array[this.index + 1] = b;
  }
  function VertexIteratorSetter_set3(a, b, c) {
    this.array[this.index] = a;
    this.array[this.index + 1] = b;
    this.array[this.index + 2] = c;
  }
  function VertexIteratorSetter_set4(a, b, c, d) {
    this.array[this.index] = a;
    this.array[this.index + 1] = b;
    this.array[this.index + 2] = c;
    this.array[this.index + 3] = d;
  }
  function VertexIterator(vertexBuffer) {
    this.vertexBuffer = vertexBuffer;
    this.buffer = this.vertexBuffer.lock();
    this.setters = [];
    this.element = {};
    var vertexFormat = this.vertexBuffer.getFormat();
    for (var i = 0;i < vertexFormat.elements.length;i++) {
      var vertexElement = vertexFormat.elements[i];
      this.setters[i] = new VertexIteratorSetter(this.buffer, vertexElement);
      this.element[vertexElement.name] = this.setters[i];
    }
  }
  VertexIterator.prototype = {next:function() {
    var i = 0;
    var setters = this.setters;
    var numSetters = this.setters.length;
    var vertexFormat = this.vertexBuffer.getFormat();
    while (i < numSetters) {
      var setter = setters[i++];
      setter.index += vertexFormat.size / setter.array.constructor.BYTES_PER_ELEMENT;
    }
  }, end:function() {
    this.vertexBuffer.unlock();
  }};
  return {VertexIterator:VertexIterator};
}());
pc.extend(pc, function() {
  var _typeSize = [];
  _typeSize[pc.ELEMENTTYPE_INT8] = 1;
  _typeSize[pc.ELEMENTTYPE_UINT8] = 1;
  _typeSize[pc.ELEMENTTYPE_INT16] = 2;
  _typeSize[pc.ELEMENTTYPE_UINT16] = 2;
  _typeSize[pc.ELEMENTTYPE_INT32] = 4;
  _typeSize[pc.ELEMENTTYPE_UINT32] = 4;
  _typeSize[pc.ELEMENTTYPE_FLOAT32] = 4;
  var VertexFormat = function(graphicsDevice, description) {
    var i, len, element;
    this.elements = [];
    this.hasUv0 = false;
    this.hasUv1 = false;
    this.hasColor = false;
    this.size = 0;
    for (i = 0, len = description.length;i < len;i++) {
      var elementDesc = description[i];
      element = {name:elementDesc.semantic, offset:0, stride:0, stream:-1, scopeId:graphicsDevice.scope.resolve(elementDesc.semantic), dataType:elementDesc.type, numComponents:elementDesc.components, normalize:elementDesc.normalize === undefined ? false : elementDesc.normalize, size:elementDesc.components * _typeSize[elementDesc.type]};
      this.elements.push(element);
      this.size += element.size;
      if (elementDesc.semantic === pc.SEMANTIC_TEXCOORD0) {
        this.hasUv0 = true;
      } else {
        if (elementDesc.semantic === pc.SEMANTIC_TEXCOORD1) {
          this.hasUv1 = true;
        } else {
          if (elementDesc.semantic === pc.SEMANTIC_COLOR) {
            this.hasColor = true;
          }
        }
      }
    }
    var offset = 0;
    for (i = 0, len = this.elements.length;i < len;i++) {
      element = this.elements[i];
      element.offset = offset;
      element.stride = this.size;
      offset += element.size;
    }
  };
  return {VertexFormat:VertexFormat};
}());
pc.extend(pc, function() {
  var VertexBuffer = function(graphicsDevice, format, numVertices, usage, initialData) {
    this.usage = usage || pc.BUFFER_STATIC;
    this.format = format;
    this.numVertices = numVertices;
    this.numBytes = format.size * numVertices;
    graphicsDevice._vram.vb += this.numBytes;
    this.device = graphicsDevice;
    var gl = this.device.gl;
    this.bufferId = gl.createBuffer();
    if (initialData && this.setData(initialData)) {
      return;
    } else {
      this.storage = new ArrayBuffer(this.numBytes);
    }
  };
  VertexBuffer.prototype = {destroy:function() {
    if (!this.bufferId) {
      return;
    }
    var gl = this.device.gl;
    gl.deleteBuffer(this.bufferId);
    this.device._vram.vb -= this.storage.byteLength;
    this.bufferId = null;
  }, getFormat:function() {
    return this.format;
  }, getUsage:function() {
    return this.usage;
  }, getNumVertices:function() {
    return this.numVertices;
  }, lock:function() {
    return this.storage;
  }, unlock:function() {
    var gl = this.device.gl;
    var glUsage;
    switch(this.usage) {
      case pc.BUFFER_STATIC:
        glUsage = gl.STATIC_DRAW;
        break;
      case pc.BUFFER_DYNAMIC:
        glUsage = gl.DYNAMIC_DRAW;
        break;
      case pc.BUFFER_STREAM:
        glUsage = gl.STREAM_DRAW;
        break;
    }
    gl.bindBuffer(gl.ARRAY_BUFFER, this.bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, this.storage, glUsage);
  }, setData:function(data) {
    if (data.byteLength !== this.numBytes) {
      console.error("VertexBuffer: wrong initial data size: expected " + this.numBytes + ", got " + data.byteLength);
      return false;
    }
    this.storage = data;
    this.unlock();
    return true;
  }};
  return {VertexBuffer:VertexBuffer};
}());
pc.extend(pc, function() {
  var IndexBuffer = function(graphicsDevice, format, numIndices, usage) {
    this.usage = usage || pc.BUFFER_STATIC;
    this.format = format;
    this.numIndices = numIndices;
    this.device = graphicsDevice;
    var gl = this.device.gl;
    this.bufferId = gl.createBuffer();
    var bytesPerIndex;
    if (format === pc.INDEXFORMAT_UINT8) {
      bytesPerIndex = 1;
      this.glFormat = gl.UNSIGNED_BYTE;
    } else {
      if (format === pc.INDEXFORMAT_UINT16) {
        bytesPerIndex = 2;
        this.glFormat = gl.UNSIGNED_SHORT;
      } else {
        if (format === pc.INDEXFORMAT_UINT32) {
          bytesPerIndex = 4;
          this.glFormat = gl.UNSIGNED_INT;
        }
      }
    }
    this.bytesPerIndex = bytesPerIndex;
    var numBytes = this.numIndices * bytesPerIndex;
    this.storage = new ArrayBuffer(numBytes);
    graphicsDevice._vram.ib += numBytes;
  };
  IndexBuffer.prototype = {destroy:function() {
    if (!this.bufferId) {
      return;
    }
    var gl = this.device.gl;
    gl.deleteBuffer(this.bufferId);
    this.device._vram.ib -= this.storage.byteLength;
    this.bufferId = null;
  }, getFormat:function() {
    return this.format;
  }, getNumIndices:function() {
    return this.numIndices;
  }, lock:function() {
    return this.storage;
  }, unlock:function() {
    var gl = this.device.gl;
    var glUsage;
    switch(this.usage) {
      case pc.BUFFER_STATIC:
        glUsage = gl.STATIC_DRAW;
        break;
      case pc.BUFFER_DYNAMIC:
        glUsage = gl.DYNAMIC_DRAW;
        break;
      case pc.BUFFER_STREAM:
        glUsage = gl.STREAM_DRAW;
        break;
    }
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.bufferId);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.storage, glUsage);
  }};
  return {IndexBuffer:IndexBuffer};
}());
pc.extend(pc, function() {
  var Texture = function(graphicsDevice, options) {
    this.device = graphicsDevice;
    this.name = null;
    this._width = 4;
    this._height = 4;
    this._pot = true;
    this._format = pc.PIXELFORMAT_R8_G8_B8_A8;
    this.rgbm = false;
    this._cubemap = false;
    this.fixCubemapSeams = false;
    this._mipmaps = true;
    this._minFilter = pc.FILTER_LINEAR_MIPMAP_LINEAR;
    this._magFilter = pc.FILTER_LINEAR;
    this._anisotropy = 1;
    this._addressU = pc.ADDRESS_REPEAT;
    this._addressV = pc.ADDRESS_REPEAT;
    if (options !== undefined) {
      this._width = options.width !== undefined ? options.width : this._width;
      this._height = options.height !== undefined ? options.height : this._height;
      this._pot = pc.math.powerOfTwo(this._width) && pc.math.powerOfTwo(this._height);
      this._format = options.format !== undefined ? options.format : this._format;
      this.rgbm = options.rgbm !== undefined ? options.rgbm : this.rgbm;
      if (options.mipmaps !== undefined) {
        this._mipmaps = options.mipmaps;
      } else {
        this._mipmaps = options.autoMipmap !== undefined ? options.autoMipmap : this._mipmaps;
      }
      this._cubemap = options.cubemap !== undefined ? options.cubemap : this._cubemap;
      this.fixCubemapSeams = options.fixCubemapSeams !== undefined ? options.fixCubemapSeams : this.fixCubemapSeams;
      this._minFilter = options.minFilter !== undefined ? options.minFilter : this._minFilter;
      this._magFilter = options.magFilter !== undefined ? options.magFilter : this._magFilter;
      this._anisotropy = options.anisotropy !== undefined ? options.anisotropy : this._anisotropy;
      this._addressU = options.addressU !== undefined ? options.addressU : this._addressU;
      this._addressV = options.addressV !== undefined ? options.addressV : this._addressV;
    }
    this._compressed = this._format === pc.PIXELFORMAT_DXT1 || this._format === pc.PIXELFORMAT_DXT3 || this._format === pc.PIXELFORMAT_DXT5 || this._format >= pc.PIXELFORMAT_ETC1;
    this._invalid = false;
    this._levels = this._cubemap ? [[null, null, null, null, null, null]] : [null];
    this._levelsUpdated = this._cubemap ? [[true, true, true, true, true, true]] : [true];
    this._lockedLevel = -1;
    this._needsUpload = true;
    this._needsMipmapsUpload = this._mipmaps;
    this._mipmapsUploaded = false;
    this._minFilterDirty = true;
    this._magFilterDirty = true;
    this._addressUDirty = true;
    this._addressVDirty = true;
    this._anisotropyDirty = true;
    this._gpuSize = 0;
  };
  Object.defineProperty(Texture.prototype, "minFilter", {get:function() {
    return this._minFilter;
  }, set:function(v) {
    if (this._minFilter !== v) {
      this._minFilter = v;
      this._minFilterDirty = true;
    }
  }});
  Object.defineProperty(Texture.prototype, "magFilter", {get:function() {
    return this._magFilter;
  }, set:function(v) {
    if (this._magFilter !== v) {
      this._magFilter = v;
      this._magFilterDirty = true;
    }
  }});
  Object.defineProperty(Texture.prototype, "addressU", {get:function() {
    return this._addressU;
  }, set:function(v) {
    if (this._addressU !== v) {
      this._addressU = v;
      this._addressUDirty = true;
    }
  }});
  Object.defineProperty(Texture.prototype, "addressV", {get:function() {
    return this._addressV;
  }, set:function(v) {
    if (this._addressV !== v) {
      this._addressV = v;
      this._addressVDirty = true;
    }
  }});
  Object.defineProperty(Texture.prototype, "autoMipmap", {get:function() {
    return this._autoMipmap;
  }, set:function(v) {
    this._autoMipmap = v;
  }});
  Object.defineProperty(Texture.prototype, "mipmaps", {get:function() {
    return this._mipmaps;
  }, set:function(v) {
    if (this._mipmaps !== v) {
      this._mipmaps = v;
      this._minFilterDirty = true;
      if (v) {
        this._needsMipmapsUpload = true;
      }
    }
  }});
  Object.defineProperty(Texture.prototype, "anisotropy", {get:function() {
    return this._anisotropy;
  }, set:function(v) {
    if (this._anisotropy !== v) {
      this._anisotropy = v;
      this._anisotropyDirty = true;
    }
  }});
  Object.defineProperty(Texture.prototype, "width", {get:function() {
    return this._width;
  }});
  Object.defineProperty(Texture.prototype, "height", {get:function() {
    return this._height;
  }});
  Object.defineProperty(Texture.prototype, "format", {get:function() {
    return this._format;
  }});
  Object.defineProperty(Texture.prototype, "cubemap", {get:function() {
    return this._cubemap;
  }});
  pc.extend(Texture.prototype, {bind:function() {
  }, destroy:function() {
    if (this._glTextureId) {
      var gl = this.device.gl;
      gl.deleteTexture(this._glTextureId);
      this.device._vram.tex -= this._gpuSize;
      this._glTextureId = null;
    }
  }, lock:function(options) {
    options = options || {level:0, face:0, mode:pc.TEXTURELOCK_WRITE};
    if (options.level === undefined) {
      options.level = 0;
    }
    if (options.face === undefined) {
      options.face = 0;
    }
    if (options.mode === undefined) {
      options.mode = pc.TEXTURELOCK_WRITE;
    }
    this._lockedLevel = options.level;
    if (this._levels[options.level] === null) {
      switch(this._format) {
        case pc.PIXELFORMAT_A8:
        case pc.PIXELFORMAT_L8:
          this._levels[options.level] = new Uint8Array(this._width * this._height);
          break;
        case pc.PIXELFORMAT_L8_A8:
          this._levels[options.level] = new Uint8Array(this._width * this._height * 2);
          break;
        case pc.PIXELFORMAT_R5_G6_B5:
        case pc.PIXELFORMAT_R5_G5_B5_A1:
        case pc.PIXELFORMAT_R4_G4_B4_A4:
          this._levels[options.level] = new Uint16Array(this._width * this._height);
          break;
        case pc.PIXELFORMAT_R8_G8_B8:
          this._levels[options.level] = new Uint8Array(this._width * this._height * 3);
          break;
        case pc.PIXELFORMAT_R8_G8_B8_A8:
          this._levels[options.level] = new Uint8Array(this._width * this._height * 4);
          break;
        case pc.PIXELFORMAT_DXT1:
          this._levels[options.level] = new Uint8Array(Math.floor((this._width + 3) / 4) * Math.floor((this._height + 3) / 4) * 8);
          break;
        case pc.PIXELFORMAT_DXT3:
        case pc.PIXELFORMAT_DXT5:
          this._levels[options.level] = new Uint8Array(Math.floor((this._width + 3) / 4) * Math.floor((this._height + 3) / 4) * 16);
          break;
        case pc.PIXELFORMAT_RGB16F:
          this._levels[options.level] = new Uint16Array(this._width * this._height * 3);
          break;
        case pc.PIXELFORMAT_RGB32F:
          this._levels[options.level] = new Float32Array(this._width * this._height * 3);
          break;
        case pc.PIXELFORMAT_RGBA16F:
          this._levels[options.level] = new Uint16Array(this._width * this._height * 4);
          break;
        case pc.PIXELFORMAT_RGBA32F:
          this._levels[options.level] = new Float32Array(this._width * this._height * 4);
          break;
      }
    }
    return this._levels[options.level];
  }, recover:function() {
  }, setSource:function(source) {
    var i;
    var invalid = false;
    var width, height;
    if (this._cubemap) {
      width = source[0] && source[0].width || 0;
      height = source[0] && source[0].height || 0;
      if (source[0]) {
        for (i = 0;i < 6;i++) {
          if (!source[i] || source[i].width !== width || source[i].height !== height || !(source[i] instanceof HTMLImageElement) && !(source[i] instanceof HTMLCanvasElement) && !(source[i] instanceof HTMLVideoElement)) {
            invalid = true;
          }
        }
      } else {
        invalid = true;
      }
      for (i = 0;i < 6;i++) {
        if (invalid || this._levels[0][i] !== source[i]) {
          this._levelsUpdated[0][i] = true;
        }
      }
    } else {
      if (!(source instanceof HTMLImageElement) && !(source instanceof HTMLCanvasElement) && !(source instanceof HTMLVideoElement)) {
        invalid = true;
      }
      if (invalid || source !== this._levels[0]) {
        this._levelsUpdated[0] = true;
      }
      width = source.width;
      height = source.height;
    }
    if (invalid) {
      this._width = 4;
      this._height = 4;
      this._pot = true;
      if (this._cubemap) {
        for (i = 0;i < 6;i++) {
          this._levels[0][i] = null;
          this._levelsUpdated[0][i] = true;
        }
      } else {
        this._levels[0] = null;
        this._levelsUpdated[0] = true;
      }
    } else {
      this._width = width;
      this._height = height;
      this._pot = pc.math.powerOfTwo(this._width) && pc.math.powerOfTwo(this._height);
      this._levels[0] = source;
    }
    if (this._invalid !== invalid || !invalid) {
      this._invalid = invalid;
      this.upload();
    }
  }, getSource:function() {
    return this._levels[0];
  }, unlock:function() {
    logASSERT(this._lockedLevel !== -1, "Attempting to unlock a texture that is not locked");
    this.upload();
    this._lockedLevel = -1;
  }, upload:function() {
    this._needsUpload = true;
    this._needsMipmapsUpload = this._mipmaps;
  }, getDds:function() {
    if (this.format !== pc.PIXELFORMAT_R8_G8_B8_A8) {
      console.error("This format is not implemented yet");
    }
    var fsize = 128;
    var i = 0;
    var j;
    var face;
    while (this._levels[i]) {
      var mipSize;
      if (!this.cubemap) {
        mipSize = this._levels[i].length;
        if (!mipSize) {
          console.error("No byte array for mip " + i);
          return;
        }
        fsize += mipSize;
      } else {
        for (face = 0;face < 6;face++) {
          if (!this._levels[i][face]) {
            console.error("No level data for mip " + i + ", face " + face);
            return;
          }
          mipSize = this._levels[i][face].length;
          if (!mipSize) {
            console.error("No byte array for mip " + i + ", face " + face);
            return;
          }
          fsize += mipSize;
        }
      }
      fsize += this._levels[i].length;
      i++;
    }
    var buff = new ArrayBuffer(fsize);
    var header = new Uint32Array(buff, 0, 128 / 4);
    var DDS_MAGIC = 542327876;
    var DDS_HEADER_SIZE = 124;
    var DDS_FLAGS_REQUIRED = 1 | 2 | 4 | 4096 | 524288;
    var DDS_FLAGS_MIPMAP = 131072;
    var DDS_PIXELFORMAT_SIZE = 32;
    var DDS_PIXELFLAGS_RGBA8 = 1 | 64;
    var DDS_CAPS_REQUIRED = 4096;
    var DDS_CAPS_MIPMAP = 4194304;
    var DDS_CAPS_COMPLEX = 8;
    var DDS_CAPS2_CUBEMAP = 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768;
    var flags = DDS_FLAGS_REQUIRED;
    if (this._levels.length > 1) {
      flags |= DDS_FLAGS_MIPMAP;
    }
    var caps = DDS_CAPS_REQUIRED;
    if (this._levels.length > 1) {
      caps |= DDS_CAPS_MIPMAP;
    }
    if (this._levels.length > 1 || this.cubemap) {
      caps |= DDS_CAPS_COMPLEX;
    }
    var caps2 = this.cubemap ? DDS_CAPS2_CUBEMAP : 0;
    header[0] = DDS_MAGIC;
    header[1] = DDS_HEADER_SIZE;
    header[2] = flags;
    header[3] = this.height;
    header[4] = this.width;
    header[5] = this.width * this.height * 4;
    header[6] = 0;
    header[7] = this._levels.length;
    for (i = 0;i < 11;i++) {
      header[8 + i] = 0;
    }
    header[19] = DDS_PIXELFORMAT_SIZE;
    header[20] = DDS_PIXELFLAGS_RGBA8;
    header[21] = 0;
    header[22] = 32;
    header[23] = 16711680;
    header[24] = 65280;
    header[25] = 255;
    header[26] = 4278190080;
    header[27] = caps;
    header[28] = caps2;
    header[29] = 0;
    header[30] = 0;
    header[31] = 0;
    var offset = 128;
    var level, mip;
    if (!this.cubemap) {
      for (i = 0;i < this._levels.length;i++) {
        level = this._levels[i];
        mip = new Uint8Array(buff, offset, level.length);
        for (j = 0;j < level.length;j++) {
          mip[j] = level[j];
        }
        offset += level.length;
      }
    } else {
      for (face = 0;face < 6;face++) {
        for (i = 0;i < this._levels.length;i++) {
          level = this._levels[i][face];
          mip = new Uint8Array(buff, offset, level.length);
          for (j = 0;j < level.length;j++) {
            mip[j] = level[j];
          }
          offset += level.length;
        }
      }
    }
    return buff;
  }});
  return {Texture:Texture};
}());
pc.extend(pc, function() {
  var defaultOptions = {depth:true, face:0};
  var RenderTarget = function(graphicsDevice, colorBuffer, options) {
    this._device = graphicsDevice;
    this._colorBuffer = colorBuffer;
    this._glFrameBuffer = null;
    this._glDepthBuffer = null;
    options = options !== undefined ? options : defaultOptions;
    this._face = options.face !== undefined ? options.face : 0;
    this._depth = options.depth !== undefined ? options.depth : true;
    this._stencil = options.stencil !== undefined ? options.stencil : false;
  };
  RenderTarget.prototype = {destroy:function() {
    var gl = this._device.gl;
    if (this._glFrameBuffer) {
      gl.deleteFramebuffer(this._glFrameBuffer);
      this._glFrameBuffer = null;
    }
    if (this._glDepthBuffer) {
      gl.deleteRenderbuffer(this._glDepthBuffer);
      this._glDepthBuffer = null;
    }
  }};
  Object.defineProperty(RenderTarget.prototype, "colorBuffer", {get:function() {
    return this._colorBuffer;
  }});
  Object.defineProperty(RenderTarget.prototype, "face", {get:function() {
    return this._face;
  }});
  Object.defineProperty(RenderTarget.prototype, "width", {get:function() {
    return this._colorBuffer.width;
  }});
  Object.defineProperty(RenderTarget.prototype, "height", {get:function() {
    return this._colorBuffer.height;
  }});
  return {RenderTarget:RenderTarget};
}());
pc.extend(pc, function() {
  var ShaderInput = function(graphicsDevice, name, type, locationId) {
    this.locationId = locationId;
    this.scopeId = graphicsDevice.scope.resolve(name);
    this.version = new pc.Version;
    if (type === pc.UNIFORMTYPE_FLOAT) {
      if (name.substr(name.length - 3) === "[0]") {
        type = pc.UNIFORMTYPE_FLOATARRAY;
      }
    }
    this.dataType = type;
    this.value = [null, null, null, null];
    this.array = [];
  };
  return {ShaderInput:ShaderInput};
}());
pc.extend(pc, function() {
  function addLineNumbers(src) {
    var chunks = src.split("\n");
    for (var i = 0, len = chunks.length;i < len;i++) {
      chunks[i] = i + 1 + ":\t" + chunks[i];
    }
    return chunks.join("\n");
  }
  function createShader(gl, type, src) {
    var shader = gl.createShader(type);
    gl.shaderSource(shader, src);
    gl.compileShader(shader);
    return shader;
  }
  function createProgram(gl, vertexShader, fragmentShader) {
    var program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    return program;
  }
  var Shader = function(graphicsDevice, definition) {
    this._refCount = 0;
    this.device = graphicsDevice;
    this.definition = definition;
    this.ready = false;
    var gl = this.device.gl;
    this.vshader = createShader(gl, gl.VERTEX_SHADER, definition.vshader);
    this.fshader = createShader(gl, gl.FRAGMENT_SHADER, definition.fshader);
    this.program = createProgram(gl, this.vshader, this.fshader);
    graphicsDevice._shaderStats.vsCompiled++;
    graphicsDevice._shaderStats.fsCompiled++;
    graphicsDevice._shaderStats.linked++;
    if (definition.tag === pc.SHADERTAG_MATERIAL) {
      graphicsDevice._shaderStats.materialShaders++;
    }
  };
  Shader.prototype = {link:function() {
    var gl = this.device.gl;
    gl.linkProgram(this.program);
    if (!gl.getShaderParameter(this.vshader, gl.COMPILE_STATUS)) {
      logERROR("Failed to compile vertex shader:\n\n" + addLineNumbers(this.definition.vshader) + "\n\n" + gl.getShaderInfoLog(this.vshader));
    }
    if (!gl.getShaderParameter(this.fshader, gl.COMPILE_STATUS)) {
      logERROR("Failed to compile fragment shader:\n\n" + addLineNumbers(this.definition.fshader) + "\n\n" + gl.getShaderInfoLog(this.fshader));
    }
    if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
      logERROR("Failed to link shader program. Error: " + gl.getProgramInfoLog(this.program));
    }
    gl.deleteShader(this.vshader);
    gl.deleteShader(this.fshader);
    this.attributes = [];
    this.uniforms = [];
    this.samplers = [];
    var i = 0;
    var info, location;
    var _typeToPc = {};
    _typeToPc[gl.BOOL] = pc.UNIFORMTYPE_BOOL;
    _typeToPc[gl.INT] = pc.UNIFORMTYPE_INT;
    _typeToPc[gl.FLOAT] = pc.UNIFORMTYPE_FLOAT;
    _typeToPc[gl.FLOAT_VEC2] = pc.UNIFORMTYPE_VEC2;
    _typeToPc[gl.FLOAT_VEC3] = pc.UNIFORMTYPE_VEC3;
    _typeToPc[gl.FLOAT_VEC4] = pc.UNIFORMTYPE_VEC4;
    _typeToPc[gl.INT_VEC2] = pc.UNIFORMTYPE_IVEC2;
    _typeToPc[gl.INT_VEC3] = pc.UNIFORMTYPE_IVEC3;
    _typeToPc[gl.INT_VEC4] = pc.UNIFORMTYPE_IVEC4;
    _typeToPc[gl.BOOL_VEC2] = pc.UNIFORMTYPE_BVEC2;
    _typeToPc[gl.BOOL_VEC3] = pc.UNIFORMTYPE_BVEC3;
    _typeToPc[gl.BOOL_VEC4] = pc.UNIFORMTYPE_BVEC4;
    _typeToPc[gl.FLOAT_MAT2] = pc.UNIFORMTYPE_MAT2;
    _typeToPc[gl.FLOAT_MAT3] = pc.UNIFORMTYPE_MAT3;
    _typeToPc[gl.FLOAT_MAT4] = pc.UNIFORMTYPE_MAT4;
    _typeToPc[gl.SAMPLER_2D] = pc.UNIFORMTYPE_TEXTURE2D;
    _typeToPc[gl.SAMPLER_CUBE] = pc.UNIFORMTYPE_TEXTURECUBE;
    var numAttributes = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
    while (i < numAttributes) {
      info = gl.getActiveAttrib(this.program, i++);
      location = gl.getAttribLocation(this.program, info.name);
      if (this.definition.attributes[info.name] === undefined) {
        console.error('Vertex shader attribute "' + info.name + '" is not mapped to a semantic in shader definition.');
      }
      var attr = new pc.ShaderInput(this.device, this.definition.attributes[info.name], _typeToPc[info.type], location);
      this.attributes.push(attr);
    }
    i = 0;
    var numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
    while (i < numUniforms) {
      info = gl.getActiveUniform(this.program, i++);
      location = gl.getUniformLocation(this.program, info.name);
      if (info.type === gl.SAMPLER_2D || info.type === gl.SAMPLER_CUBE) {
        this.samplers.push(new pc.ShaderInput(this.device, info.name, _typeToPc[info.type], location));
      } else {
        this.uniforms.push(new pc.ShaderInput(this.device, info.name, _typeToPc[info.type], location));
      }
    }
    this.ready = true;
  }, destroy:function() {
    if (this.program) {
      var gl = this.device.gl;
      gl.deleteProgram(this.program);
      this.program = null;
      this.device.removeShaderFromCache(this);
    }
  }};
  return {Shader:Shader};
}());
pc.extend(pc, function() {
  var ProgramLibrary = function(device) {
    this._device = device;
    this._cache = {};
    this._generators = {};
    this._isClearingCache = false;
  };
  ProgramLibrary.prototype.register = function(name, generator) {
    if (!this.isRegistered(name)) {
      this._generators[name] = generator;
    }
  };
  ProgramLibrary.prototype.unregister = function(name) {
    if (this.isRegistered(name)) {
      delete this._generators[name];
    }
  };
  ProgramLibrary.prototype.isRegistered = function(name) {
    var generator = this._generators[name];
    return generator !== undefined;
  };
  ProgramLibrary.prototype.getProgram = function(name, options) {
    var generator = this._generators[name];
    if (generator === undefined) {
      logERROR("No program library functions registered for: " + name);
      return null;
    }
    var gd = this._device;
    var key = generator.generateKey(gd, options);
    var shader = this._cache[key];
    if (!shader) {
      var shaderDefinition = generator.createShaderDefinition(gd, options);
      shader = this._cache[key] = new pc.Shader(gd, shaderDefinition);
    }
    return shader;
  };
  ProgramLibrary.prototype.clearCache = function() {
    var cache = this._cache;
    this._isClearingCache = true;
    for (var key in cache) {
      if (cache.hasOwnProperty(key)) {
        cache[key].destroy();
      }
    }
    this._cache = {};
    this._isClearingCache = false;
  };
  ProgramLibrary.prototype.removeFromCache = function(shader) {
    if (this._isClearingCache) {
      return;
    }
    var cache = this._cache;
    for (var key in cache) {
      if (cache.hasOwnProperty(key)) {
        if (cache[key] === shader) {
          delete cache[key];
          break;
        }
      }
    }
  };
  return {ProgramLibrary:ProgramLibrary};
}());
pc.extend(pc, function() {
  var EVENT_RESIZE = "resizecanvas";
  var uniformValue;
  var scopeX, scopeY, scopeZ, scopeW;
  function UnsupportedBrowserError(message) {
    this.name = "UnsupportedBrowserError";
    this.message = message || "";
  }
  UnsupportedBrowserError.prototype = Error.prototype;
  function ContextCreationError(message) {
    this.name = "ContextCreationError";
    this.message = message || "";
  }
  ContextCreationError.prototype = Error.prototype;
  var _contextLostHandler = function() {
    logWARNING("Context lost.");
  };
  var _contextRestoredHandler = function() {
    logINFO("Context restored.");
  };
  var _createContext = function(canvas, options) {
    var names = ["webgl", "experimental-webgl"];
    var context = null;
    options = options || {};
    options.stencil = true;
    for (var i = 0;i < names.length;i++) {
      try {
        context = canvas.getContext(names[i], options);
      } catch (e) {
      }
      if (context) {
        break;
      }
    }
    return context;
  };
  var _downsampleImage = function(image, size) {
    var srcW = image.width;
    var srcH = image.height;
    if (srcW > size || srcH > size) {
      var scale = size / Math.max(srcW, srcH);
      var dstW = Math.floor(srcW * scale);
      var dstH = Math.floor(srcH * scale);
      console.warn("Image dimensions larger than max supported texture size of " + size + ". " + "Resizing from " + srcW + ", " + srcH + " to " + dstW + ", " + dstH + ".");
      var canvas = document.createElement("canvas");
      canvas.width = dstW;
      canvas.height = dstH;
      var context = canvas.getContext("2d");
      context.drawImage(image, 0, 0, srcW, srcH, 0, 0, dstW, dstH);
      return canvas;
    }
    return image;
  };
  function _isIE() {
    var ua = window.navigator.userAgent;
    var msie = ua.indexOf("MSIE ");
    var trident = navigator.userAgent.match(/Trident.*rv\:11\./);
    return msie > 0 || !!trident;
  }
  var _pixelFormat2Size = null;
  function gpuTexSize(gl, tex) {
    if (!_pixelFormat2Size) {
      _pixelFormat2Size = {};
      _pixelFormat2Size[pc.PIXELFORMAT_A8] = 1;
      _pixelFormat2Size[pc.PIXELFORMAT_L8] = 1;
      _pixelFormat2Size[pc.PIXELFORMAT_L8_A8] = 1;
      _pixelFormat2Size[pc.PIXELFORMAT_R5_G6_B5] = 2;
      _pixelFormat2Size[pc.PIXELFORMAT_R5_G5_B5_A1] = 2;
      _pixelFormat2Size[pc.PIXELFORMAT_R4_G4_B4_A4] = 2;
      _pixelFormat2Size[pc.PIXELFORMAT_R8_G8_B8] = 4;
      _pixelFormat2Size[pc.PIXELFORMAT_R8_G8_B8_A8] = 4;
      _pixelFormat2Size[pc.PIXELFORMAT_RGB16F] = 8;
      _pixelFormat2Size[pc.PIXELFORMAT_RGBA16F] = 8;
      _pixelFormat2Size[pc.PIXELFORMAT_RGB32F] = 16;
      _pixelFormat2Size[pc.PIXELFORMAT_RGBA32F] = 16;
    }
    var mips = 1;
    if (tex._pot && (tex._mipmaps || tex._minFilter === gl.NEAREST_MIPMAP_NEAREST || tex._minFilter === gl.NEAREST_MIPMAP_LINEAR || tex._minFilter === gl.LINEAR_MIPMAP_NEAREST || tex._minFilter === gl.LINEAR_MIPMAP_LINEAR) && !(tex._compressed && tex._levels.length === 1)) {
      mips = Math.round(Math.log2(Math.max(tex._width, tex._height)) + 1);
    }
    var mipWidth = tex._width;
    var mipHeight = tex._height;
    var size = 0;
    for (var i = 0;i < mips;i++) {
      if (!tex._compressed) {
        size += mipWidth * mipHeight * _pixelFormat2Size[tex._format];
      } else {
        if (tex._format === pc.PIXELFORMAT_ETC1) {
          size += Math.floor((mipWidth + 3) / 4) * Math.floor((mipHeight + 3) / 4) * 8;
        } else {
          if (tex._format === pc.PIXELFORMAT_PVRTC_2BPP_RGB_1 || tex._format === pc.PIXELFORMAT_PVRTC_2BPP_RGBA_1) {
            size += Math.max(mipWidth, 16) * Math.max(mipHeight, 8) / 4;
          } else {
            if (tex._format === pc.PIXELFORMAT_PVRTC_4BPP_RGB_1 || tex._format === pc.PIXELFORMAT_PVRTC_4BPP_RGBA_1) {
              size += Math.max(mipWidth, 8) * Math.max(mipHeight, 8) / 2;
            } else {
              var DXT_BLOCK_WIDTH = 4;
              var DXT_BLOCK_HEIGHT = 4;
              var blockSize = tex._format === pc.PIXELFORMAT_DXT1 ? 8 : 16;
              var numBlocksAcross = Math.floor((mipWidth + DXT_BLOCK_WIDTH - 1) / DXT_BLOCK_WIDTH);
              var numBlocksDown = Math.floor((mipHeight + DXT_BLOCK_HEIGHT - 1) / DXT_BLOCK_HEIGHT);
              var numBlocks = numBlocksAcross * numBlocksDown;
              size += numBlocks * blockSize;
            }
          }
        }
      }
      mipWidth = Math.max(mipWidth * .5, 1);
      mipHeight = Math.max(mipHeight * .5, 1);
    }
    if (tex._cubemap) {
      size *= 6;
    }
    return size;
  }
  function testRenderable(gl, ext, pixelFormat) {
    var __texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, __texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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);
    var __width = 2;
    var __height = 2;
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, __width, __height, 0, gl.RGBA, pixelFormat, null);
    var __fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, __fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, __texture, 0);
    gl.bindTexture(gl.TEXTURE_2D, null);
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
      gl.deleteTexture(__texture);
      return false;
    }
    gl.deleteTexture(__texture);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    return true;
  }
  var GraphicsDevice = function(canvas, options) {
    this.gl = undefined;
    this.canvas = canvas;
    this.shader = null;
    this.indexBuffer = null;
    this.vertexBuffers = [];
    this.vbOffsets = [];
    this.precision = "highp";
    this._enableAutoInstancing = false;
    this.autoInstancingMaxObjects = 16384;
    this.attributesInvalidated = true;
    this.boundBuffer = null;
    this.instancedAttribs = {};
    this.enabledAttributes = {};
    this.textureUnits = [];
    this.commitFunction = {};
    this._maxPixelRatio = 1;
    this._width = 0;
    this._height = 0;
    this.updateClientRect();
    if (!window.WebGLRenderingContext) {
      throw new pc.UnsupportedBrowserError;
    }
    if (canvas) {
      this.gl = _createContext(canvas, options);
    }
    if (!this.gl) {
      throw new pc.ContextCreationError;
    }
    var gl = this.gl;
    (function() {
      var i;
      canvas.addEventListener("webglcontextlost", _contextLostHandler, false);
      canvas.addEventListener("webglcontextrestored", _contextRestoredHandler, false);
      this.canvas = canvas;
      this.shader = null;
      this.indexBuffer = null;
      this.vertexBuffers = [];
      this.vbOffsets = [];
      this.precision = "highp";
      this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
      this.maxCubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
      this.maxRenderBufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
      if (gl.getShaderPrecisionFormat) {
        var vertexShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT);
        var vertexShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT);
        var vertexShaderPrecisionLowpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT);
        var fragmentShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
        var fragmentShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);
        var fragmentShaderPrecisionLowpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_FLOAT);
        var vertexShaderPrecisionHighpInt = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_INT);
        var vertexShaderPrecisionMediumpInt = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_INT);
        var vertexShaderPrecisionLowpInt = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_INT);
        var fragmentShaderPrecisionHighpInt = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT);
        var fragmentShaderPrecisionMediumpInt = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_INT);
        var fragmentShaderPrecisionLowpInt = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_INT);
        var highpAvailable = vertexShaderPrecisionHighpFloat.precision > 0 && fragmentShaderPrecisionHighpFloat.precision > 0;
        var mediumpAvailable = vertexShaderPrecisionMediumpFloat.precision > 0 && fragmentShaderPrecisionMediumpFloat.precision > 0;
        if (!highpAvailable) {
          if (mediumpAvailable) {
            this.precision = "mediump";
            console.warn("WARNING: highp not supported, using mediump");
          } else {
            this.precision = "lowp";
            console.warn("WARNING: highp and mediump not supported, using lowp");
          }
        }
      }
      this.maxPrecision = this.precision;
      this.defaultClearOptions = {color:[0, 0, 0, 1], depth:1, stencil:0, flags:pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH};
      this.glAddress = [gl.REPEAT, gl.CLAMP_TO_EDGE, gl.MIRRORED_REPEAT];
      this.glBlendEquation = [gl.FUNC_ADD, gl.FUNC_SUBTRACT, gl.FUNC_REVERSE_SUBTRACT];
      this.glBlendFunction = [gl.ZERO, gl.ONE, gl.SRC_COLOR, gl.ONE_MINUS_SRC_COLOR, gl.DST_COLOR, gl.ONE_MINUS_DST_COLOR, gl.SRC_ALPHA, gl.SRC_ALPHA_SATURATE, gl.ONE_MINUS_SRC_ALPHA, gl.DST_ALPHA, gl.ONE_MINUS_DST_ALPHA];
      this.glComparison = [gl.NEVER, gl.LESS, gl.EQUAL, gl.LEQUAL, gl.GREATER, gl.NOTEQUAL, gl.GEQUAL, gl.ALWAYS];
      this.glStencilOp = [gl.KEEP, gl.ZERO, gl.REPLACE, gl.INCR, gl.INCR_WRAP, gl.DECR, gl.DECR_WRAP, gl.INVERT];
      this.glClearFlag = [0, gl.COLOR_BUFFER_BIT, gl.DEPTH_BUFFER_BIT, gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, gl.STENCIL_BUFFER_BIT, gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT, gl.STENCIL_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT];
      this.glCull = [0, gl.BACK, gl.FRONT, gl.FRONT_AND_BACK];
      this.glFilter = [gl.NEAREST, gl.LINEAR, gl.NEAREST_MIPMAP_NEAREST, gl.NEAREST_MIPMAP_LINEAR, gl.LINEAR_MIPMAP_NEAREST, gl.LINEAR_MIPMAP_LINEAR];
      this.glPrimitive = [gl.POINTS, gl.LINES, gl.LINE_LOOP, gl.LINE_STRIP, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN];
      this.glType = [gl.BYTE, gl.UNSIGNED_BYTE, gl.SHORT, gl.UNSIGNED_SHORT, gl.INT, gl.UNSIGNED_INT, gl.FLOAT];
      this.unmaskedRenderer = null;
      this.unmaskedVendor = null;
      this.extRendererInfo = gl.getExtension("WEBGL_debug_renderer_info");
      if (this.extRendererInfo) {
        this.unmaskedRenderer = gl.getParameter(this.extRendererInfo.UNMASKED_RENDERER_WEBGL);
        this.unmaskedVendor = gl.getParameter(this.extRendererInfo.UNMASKED_VENDOR_WEBGL);
      }
      this.extTextureFloat = gl.getExtension("OES_texture_float");
      this.extTextureFloatLinear = gl.getExtension("OES_texture_float_linear");
      this.extTextureHalfFloat = gl.getExtension("OES_texture_half_float");
      this.extTextureHalfFloatLinear = gl.getExtension("OES_texture_half_float_linear");
      this.extUintElement = gl.getExtension("OES_element_index_uint");
      this.maxVertexTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
      this.supportsBoneTextures = this.extTextureFloat && this.maxVertexTextures > 0;
      this.extTextureLod = gl.getExtension("EXT_shader_texture_lod");
      this.fragmentUniformsCount = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
      this.samplerCount = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
      this.useTexCubeLod = this.extTextureLod && this.samplerCount < 16;
      this.extDepthTexture = null;
      this.extStandardDerivatives = gl.getExtension("OES_standard_derivatives");
      if (this.extStandardDerivatives) {
        gl.hint(this.extStandardDerivatives.FRAGMENT_SHADER_DERIVATIVE_HINT_OES, gl.NICEST);
      }
      this.extTextureFilterAnisotropic = gl.getExtension("EXT_texture_filter_anisotropic");
      if (!this.extTextureFilterAnisotropic) {
        this.extTextureFilterAnisotropic = gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic");
      }
      this.extCompressedTextureS3TC = gl.getExtension("WEBGL_compressed_texture_s3tc");
      if (!this.extCompressedTextureS3TC) {
        this.extCompressedTextureS3TC = gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc");
      }
      if (this.extCompressedTextureS3TC && _isIE()) {
        this.extCompressedTextureS3TC = false;
      }
      if (this.extCompressedTextureS3TC) {
        var formats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
        for (i = 0;i < formats.length;i++) {
          switch(formats[i]) {
            case this.extCompressedTextureS3TC.COMPRESSED_RGB_S3TC_DXT1_EXT:
              break;
            case this.extCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT1_EXT:
              break;
            case this.extCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT3_EXT:
              break;
            case this.extCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT5_EXT:
              break;
            default:
              break;
          }
        }
      }
      this.extInstancing = gl.getExtension("ANGLE_instanced_arrays");
      this.extCompressedTextureETC1 = gl.getExtension("WEBGL_compressed_texture_etc1");
      this.extCompressedTexturePVRTC = gl.getExtension("WEBGL_compressed_texture_pvrtc") || gl.getExtension("WEBKIT_WEBGL_compressed_texture_pvrtc");
      this.extDrawBuffers = gl.getExtension("EXT_draw_buffers");
      this.maxDrawBuffers = this.extDrawBuffers ? gl.getParameter(this.extDrawBuffers.MAX_DRAW_BUFFERS_EXT) : 1;
      this.maxColorAttachments = this.extDrawBuffers ? gl.getParameter(this.extDrawBuffers.MAX_COLOR_ATTACHMENTS_EXT) : 1;
      var contextAttribs = gl.getContextAttributes();
      this.supportsMsaa = contextAttribs.antialias;
      this.supportsStencil = contextAttribs.stencil;
      this.renderTarget = null;
      this.scope = new pc.ScopeSpace("Device");
      this.commitFunction = {};
      this.commitFunction[pc.UNIFORMTYPE_BOOL] = function(uniform, value) {
        if (uniform.value !== value) {
          gl.uniform1i(uniform.locationId, value);
          uniform.value = value;
        }
      };
      this.commitFunction[pc.UNIFORMTYPE_INT] = this.commitFunction[pc.UNIFORMTYPE_BOOL];
      this.commitFunction[pc.UNIFORMTYPE_FLOAT] = function(uniform, value) {
        if (uniform.value !== value) {
          gl.uniform1f(uniform.locationId, value);
          uniform.value = value;
        }
      };
      this.commitFunction[pc.UNIFORMTYPE_VEC2] = function(uniform, value) {
        uniformValue = uniform.value;
        scopeX = value[0];
        scopeY = value[1];
        if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {
          gl.uniform2fv(uniform.locationId, value);
          uniformValue[0] = scopeX;
          uniformValue[1] = scopeY;
        }
      };
      this.commitFunction[pc.UNIFORMTYPE_VEC3] = function(uniform, value) {
        uniformValue = uniform.value;
        scopeX = value[0];
        scopeY = value[1];
        scopeZ = value[2];
        if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {
          gl.uniform3fv(uniform.locationId, value);
          uniformValue[0] = scopeX;
          uniformValue[1] = scopeY;
          uniformValue[2] = scopeZ;
        }
      };
      this.commitFunction[pc.UNIFORMTYPE_VEC4] = function(uniform, value) {
        uniformValue = uniform.value;
        scopeX = value[0];
        scopeY = value[1];
        scopeZ = value[2];
        scopeW = value[3];
        if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {
          gl.uniform4fv(uniform.locationId, value);
          uniformValue[0] = scopeX;
          uniformValue[1] = scopeY;
          uniformValue[2] = scopeZ;
          uniformValue[3] = scopeW;
        }
      };
      this.commitFunction[pc.UNIFORMTYPE_IVEC2] = function(uniform, value) {
        uniformValue = uniform.value;
        scopeX = value[0];
        scopeY = value[1];
        if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {
          gl.uniform2iv(uniform.locationId, value);
          uniformValue[0] = scopeX;
          uniformValue[1] = scopeY;
        }
      };
      this.commitFunction[pc.UNIFORMTYPE_BVEC2] = this.commitFunction[pc.UNIFORMTYPE_IVEC2];
      this.commitFunction[pc.UNIFORMTYPE_IVEC3] = function(uniform, value) {
        uniformValue = uniform.value;
        scopeX = value[0];
        scopeY = value[1];
        scopeZ = value[2];
        if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {
          gl.uniform3iv(uniform.locationId, value);
          uniformValue[0] = scopeX;
          uniformValue[1] = scopeY;
          uniformValue[2] = scopeZ;
        }
      };
      this.commitFunction[pc.UNIFORMTYPE_BVEC3] = this.commitFunction[pc.UNIFORMTYPE_IVEC3];
      this.commitFunction[pc.UNIFORMTYPE_IVEC4] = function(uniform, value) {
        uniformValue = uniform.value;
        scopeX = value[0];
        scopeY = value[1];
        scopeZ = value[2];
        scopeW = value[3];
        if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {
          gl.uniform4iv(uniform.locationId, value);
          uniformValue[0] = scopeX;
          uniformValue[1] = scopeY;
          uniformValue[2] = scopeZ;
          uniformValue[3] = scopeW;
        }
      };
      this.commitFunction[pc.UNIFORMTYPE_BVEC4] = this.commitFunction[pc.UNIFORMTYPE_IVEC4];
      this.commitFunction[pc.UNIFORMTYPE_MAT2] = function(uniform, value) {
        gl.uniformMatrix2fv(uniform.locationId, false, value);
      };
      this.commitFunction[pc.UNIFORMTYPE_MAT3] = function(uniform, value) {
        gl.uniformMatrix3fv(uniform.locationId, false, value);
      };
      this.commitFunction[pc.UNIFORMTYPE_MAT4] = function(uniform, value) {
        gl.uniformMatrix4fv(uniform.locationId, false, value);
      };
      this.commitFunction[pc.UNIFORMTYPE_FLOATARRAY] = function(uniform, value) {
        gl.uniform1fv(uniform.locationId, value);
      };
      this.setBlending(false);
      this.setBlendFunction(pc.BLENDMODE_ONE, pc.BLENDMODE_ZERO);
      this.setBlendEquation(pc.BLENDEQUATION_ADD);
      this.setColorWrite(true, true, true, true);
      this.cullMode = pc.CULLFACE_NONE;
      this.setCullMode(pc.CULLFACE_BACK);
      this.setDepthTest(true);
      this.setDepthWrite(true);
      this.setStencilTest(false);
      this.setStencilFunc(pc.FUNC_ALWAYS, 0, 255);
      this.setStencilOperation(pc.STENCILOP_KEEP, pc.STENCILOP_KEEP, pc.STENCILOP_KEEP);
      this.setClearDepth(1);
      this.setClearColor(0, 0, 0, 0);
      this.setClearStencil(0);
      gl.enable(gl.SCISSOR_TEST);
      this.programLib = new pc.ProgramLibrary(this);
      for (var generator in pc.programlib) {
        this.programLib.register(generator, pc.programlib[generator]);
      }
      var numUniforms = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
      numUniforms -= 4 * 4;
      numUniforms -= 8;
      numUniforms -= 1;
      numUniforms -= 4 * 4;
      this.boneLimit = Math.floor(numUniforms / 4);
      this.boneLimit = Math.min(this.boneLimit, 128);
      if (this.unmaskedRenderer === "Mali-450 MP") {
        this.boneLimit = 34;
      } else {
        if (this.unmaskedRenderer === "Apple A8 GPU") {
          this.forceCpuParticles = true;
        }
      }
      pc.events.attach(this);
      this.vx = this.vy = this.vw = this.vh = 0;
      this.sx = this.sy = this.sw = this.sh = 0;
      this.boundBuffer = null;
      this.instancedAttribs = {};
      this.activeFramebuffer = null;
      this.activeTexture = 0;
      this.textureUnits = [];
      this.attributesInvalidated = true;
      this.enabledAttributes = {};
      this._drawCallsPerFrame = 0;
      this._shaderSwitchesPerFrame = 0;
      this._primsPerFrame = [];
      for (i = pc.PRIMITIVE_POINTS;i <= pc.PRIMITIVE_TRIFAN;i++) {
        this._primsPerFrame[i] = 0;
      }
      this._renderTargetCreationTime = 0;
      this._vram = {tex:0, vb:0, ib:0};
      this._shaderStats = {vsCompiled:0, fsCompiled:0, linked:0, materialShaders:0, compileTime:0};
      var bufferId = gl.createBuffer();
      var storage = new ArrayBuffer(16);
      gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
      gl.bufferData(gl.ARRAY_BUFFER, storage, gl.STATIC_DRAW);
      gl.getError();
      gl.vertexAttribPointer(0, 4, gl.UNSIGNED_BYTE, false, 4, 0);
      this.supportsUnsignedByte = gl.getError() === 0;
      gl.deleteBuffer(bufferId);
      if (!pc._benchmarked) {
        if (this.extTextureFloat) {
          this.extTextureFloatRenderable = testRenderable(gl, this.extTextureFloat, gl.FLOAT);
        }
        if (this.extTextureHalfFloat) {
          this.extTextureHalfFloatRenderable = testRenderable(gl, this.extTextureHalfFloat, this.extTextureHalfFloat.HALF_FLOAT_OES);
        }
        if (this.extTextureFloatRenderable) {
          var device = this;
          var chunks = pc.shaderChunks;
          var test1 = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, chunks.precisionTestPS, "ptest1");
          var test2 = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, chunks.precisionTest2PS, "ptest2");
          var size = 1;
          var tex = new pc.Texture(device, {format:pc.PIXELFORMAT_RGBA32F, width:size, height:size, mipmaps:false, minFilter:pc.FILTER_NEAREST, magFilter:pc.FILTER_NEAREST});
          var targ = new pc.RenderTarget(device, tex, {depth:false});
          pc.drawQuadWithShader(device, targ, test1);
          var tex2 = new pc.Texture(device, {format:pc.PIXELFORMAT_R8_G8_B8_A8, width:size, height:size, mipmaps:false, minFilter:pc.FILTER_NEAREST, magFilter:pc.FILTER_NEAREST});
          var targ2 = new pc.RenderTarget(device, tex2, {depth:false});
          var constantTexSource = device.scope.resolve("source");
          constantTexSource.setValue(tex);
          pc.drawQuadWithShader(device, targ2, test2);
          var pixels = new Uint8Array(size * size * 4);
          gl.bindFramebuffer(gl.FRAMEBUFFER, targ2._glFrameBuffer);
          gl.readPixels(0, 0, size, size, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
          var x = pixels[0] / 255;
          var y = pixels[1] / 255;
          var z = pixels[2] / 255;
          var w = pixels[3] / 255;
          var f = x / (256 * 256 * 256) + y / (256 * 256) + z / 256 + w;
          this.extTextureFloatHighPrecision = f === 0;
          tex.destroy();
          targ.destroy();
          tex2.destroy();
          targ2.destroy();
          pc.destroyPostEffectQuad();
          gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        }
        pc.extTextureFloatRenderable = this.extTextureFloatRenderable;
        pc.extTextureHalfFloatRenderable = this.extTextureHalfFloatRenderable;
        pc.extTextureFloatHighPrecision = this.extTextureFloatHighPrecision;
        pc._benchmarked = true;
      } else {
        this.extTextureFloatRenderable = pc.extTextureFloatRenderable;
        this.extTextureHalfFloatRenderable = pc.extTextureHalfFloatRenderable;
        this.extTextureFloatHighPrecision = pc.extTextureFloatHighPrecision;
      }
    }).call(this);
  };
  GraphicsDevice.prototype = {updateClientRect:function() {
    this.clientRect = this.canvas.getBoundingClientRect();
  }, setViewport:function(x, y, w, h) {
    if (this.vx !== x || this.vy !== y || this.vw !== w || this.vh !== h) {
      this.gl.viewport(x, y, w, h);
      this.vx = x;
      this.vy = y;
      this.vw = w;
      this.vh = h;
    }
  }, setScissor:function(x, y, w, h) {
    if (this.sx !== x || this.sy !== y || this.sw !== w || this.sh !== h) {
      this.gl.scissor(x, y, w, h);
      this.sx = x;
      this.sy = y;
      this.sw = w;
      this.sh = h;
    }
  }, getProgramLibrary:function() {
    return this.programLib;
  }, setProgramLibrary:function(programLib) {
    this.programLib = programLib;
  }, setFramebuffer:function(fb) {
    if (this.activeFramebuffer !== fb) {
      this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, fb);
      this.activeFramebuffer = fb;
    }
  }, updateBegin:function() {
    var gl = this.gl;
    this.boundBuffer = null;
    this.indexBuffer = null;
    var target = this.renderTarget;
    if (target) {
      if (!target._glFrameBuffer) {
        target._glFrameBuffer = gl.createFramebuffer();
        this.setFramebuffer(target._glFrameBuffer);
        var colorBuffer = target._colorBuffer;
        if (!colorBuffer._glTextureId) {
          colorBuffer._width = Math.min(colorBuffer.width, this.maxRenderBufferSize);
          colorBuffer._height = Math.min(colorBuffer.height, this.maxRenderBufferSize);
          this.setTexture(colorBuffer, 0);
        }
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer._glTextureId, 0);
        if (target._depth) {
          if (!target._glDepthBuffer) {
            target._glDepthBuffer = gl.createRenderbuffer();
          }
          gl.bindRenderbuffer(gl.RENDERBUFFER, target._glDepthBuffer);
          if (target._stencil) {
            gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, target.width, target.height);
            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, target._glDepthBuffer);
          } else {
            gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, target.width, target.height);
            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, target._glDepthBuffer);
          }
          gl.bindRenderbuffer(gl.RENDERBUFFER, null);
        }
        var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
        switch(status) {
          case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
            console.error("ERROR: FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
            break;
          case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
            console.error("ERROR: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
            break;
          case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
            console.error("ERROR: FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
            break;
          case gl.FRAMEBUFFER_UNSUPPORTED:
            console.error("ERROR: FRAMEBUFFER_UNSUPPORTED");
            break;
          case gl.FRAMEBUFFER_COMPLETE:
            break;
          default:
            break;
        }
      } else {
        this.setFramebuffer(target._glFrameBuffer);
      }
    } else {
      this.setFramebuffer(null);
    }
    for (var i = 0;i < 16;i++) {
      this.textureUnits[i] = null;
    }
  }, updateEnd:function() {
    var gl = this.gl;
    var target = this.renderTarget;
    if (target) {
      this.setFramebuffer(null);
      var colorBuffer = target._colorBuffer;
      if (colorBuffer._glTextureId && colorBuffer.mipmaps && colorBuffer._pot) {
        gl.bindTexture(colorBuffer._glTarget, colorBuffer._glTextureId);
        gl.generateMipmap(colorBuffer._glTarget);
      }
    }
  }, initializeTexture:function(texture) {
    var gl = this.gl;
    var ext;
    texture._glTextureId = gl.createTexture();
    texture._glTarget = texture._cubemap ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
    switch(texture._format) {
      case pc.PIXELFORMAT_A8:
        texture._glFormat = gl.ALPHA;
        texture._glInternalFormat = gl.ALPHA;
        texture._glPixelType = gl.UNSIGNED_BYTE;
        break;
      case pc.PIXELFORMAT_L8:
        texture._glFormat = gl.LUMINANCE;
        texture._glInternalFormat = gl.LUMINANCE;
        texture._glPixelType = gl.UNSIGNED_BYTE;
        break;
      case pc.PIXELFORMAT_L8_A8:
        texture._glFormat = gl.LUMINANCE_ALPHA;
        texture._glInternalFormat = gl.LUMINANCE_ALPHA;
        texture._glPixelType = gl.UNSIGNED_BYTE;
        break;
      case pc.PIXELFORMAT_R5_G6_B5:
        texture._glFormat = gl.RGB;
        texture._glInternalFormat = gl.RGB;
        texture._glPixelType = gl.UNSIGNED_SHORT_5_6_5;
        break;
      case pc.PIXELFORMAT_R5_G5_B5_A1:
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = gl.RGBA;
        texture._glPixelType = gl.UNSIGNED_SHORT_5_5_5_1;
        break;
      case pc.PIXELFORMAT_R4_G4_B4_A4:
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = gl.RGBA;
        texture._glPixelType = gl.UNSIGNED_SHORT_4_4_4_4;
        break;
      case pc.PIXELFORMAT_R8_G8_B8:
        texture._glFormat = gl.RGB;
        texture._glInternalFormat = gl.RGB;
        texture._glPixelType = gl.UNSIGNED_BYTE;
        break;
      case pc.PIXELFORMAT_R8_G8_B8_A8:
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = gl.RGBA;
        texture._glPixelType = gl.UNSIGNED_BYTE;
        break;
      case pc.PIXELFORMAT_DXT1:
        ext = this.extCompressedTextureS3TC;
        texture._glFormat = gl.RGB;
        texture._glInternalFormat = ext.COMPRESSED_RGB_S3TC_DXT1_EXT;
        break;
      case pc.PIXELFORMAT_DXT3:
        ext = this.extCompressedTextureS3TC;
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = ext.COMPRESSED_RGBA_S3TC_DXT3_EXT;
        break;
      case pc.PIXELFORMAT_DXT5:
        ext = this.extCompressedTextureS3TC;
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = ext.COMPRESSED_RGBA_S3TC_DXT5_EXT;
        break;
      case pc.PIXELFORMAT_ETC1:
        ext = this.extCompressedTextureETC1;
        texture._glFormat = gl.RGB;
        texture._glInternalFormat = ext.COMPRESSED_RGB_ETC1_WEBGL;
        break;
      case pc.PIXELFORMAT_PVRTC_2BPP_RGB_1:
        ext = this.extCompressedTexturePVRTC;
        texture._glFormat = gl.RGB;
        texture._glInternalFormat = ext.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
        break;
      case pc.PIXELFORMAT_PVRTC_2BPP_RGBA_1:
        ext = this.extCompressedTexturePVRTC;
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = ext.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
        break;
      case pc.PIXELFORMAT_PVRTC_4BPP_RGB_1:
        ext = this.extCompressedTexturePVRTC;
        texture._glFormat = gl.RGB;
        texture._glInternalFormat = ext.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
        break;
      case pc.PIXELFORMAT_PVRTC_4BPP_RGBA_1:
        ext = this.extCompressedTexturePVRTC;
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = ext.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
        break;
      case pc.PIXELFORMAT_RGB16F:
        ext = this.extTextureHalfFloat;
        texture._glFormat = gl.RGB;
        texture._glInternalFormat = gl.RGB;
        texture._glPixelType = ext.HALF_FLOAT_OES;
        break;
      case pc.PIXELFORMAT_RGBA16F:
        ext = this.extTextureHalfFloat;
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = gl.RGBA;
        texture._glPixelType = ext.HALF_FLOAT_OES;
        break;
      case pc.PIXELFORMAT_RGB32F:
        texture._glFormat = gl.RGB;
        texture._glInternalFormat = gl.RGB;
        texture._glPixelType = gl.FLOAT;
        break;
      case pc.PIXELFORMAT_RGBA32F:
        texture._glFormat = gl.RGBA;
        texture._glInternalFormat = gl.RGBA;
        texture._glPixelType = gl.FLOAT;
        break;
    }
  }, uploadTexture:function(texture) {
    var gl = this.gl;
    if (!texture._needsUpload && (texture._needsMipmapsUpload && texture._mipmapsUploaded || !texture._pot)) {
      return;
    }
    var mipLevel = 0;
    var mipObject;
    var resMult;
    while (texture._levels[mipLevel] || mipLevel === 0) {
      if (!texture._needsUpload && mipLevel === 0) {
        mipLevel++;
        continue;
      } else {
        if (mipLevel && (!texture._needsMipmapsUpload || !texture._mipmaps)) {
          break;
        }
      }
      mipObject = texture._levels[mipLevel];
      if (mipLevel == 1 && !texture._compressed) {
        gl.generateMipmap(texture._glTarget);
        texture._mipmapsUploaded = true;
        if (texture.name) {
          "generateMipmap 1", mipLevel;
        }
      }
      if (texture._cubemap) {
        var face;
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
        if (mipObject[0] instanceof HTMLCanvasElement || mipObject[0] instanceof HTMLImageElement || mipObject[0] instanceof HTMLVideoElement) {
          for (face = 0;face < 6;face++) {
            if (!texture._levelsUpdated[0][face]) {
              continue;
            }
            var src = mipObject[face];
            if (src instanceof HTMLImageElement) {
              if (src.width > this.maxCubeMapSize || src.height > this.maxCubeMapSize) {
                src = _downsampleImage(src, this.maxCubeMapSize);
                if (mipLevel === 0) {
                  texture.width = src.width;
                  texture.height = src.height;
                }
              }
            }
            gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, texture._glInternalFormat, texture._glFormat, texture._glPixelType, src);
          }
        } else {
          resMult = 1 / Math.pow(2, mipLevel);
          for (face = 0;face < 6;face++) {
            if (!texture._levelsUpdated[0][face]) {
              continue;
            }
            var texData = mipObject[face];
            if (texture._compressed) {
              gl.compressedTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, texture._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, texData);
            } else {
              gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, texture._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, texture._glFormat, texture._glPixelType, texData);
            }
          }
        }
      } else {
        if (mipObject instanceof HTMLCanvasElement || mipObject instanceof HTMLImageElement || mipObject instanceof HTMLVideoElement) {
          if (mipObject instanceof HTMLImageElement) {
            if (mipObject.width > this.maxTextureSize || mipObject.height > this.maxTextureSize) {
              mipObject = _downsampleImage(mipObject, this.maxTextureSize);
              if (mipLevel === 0) {
                texture.width = mipObject.width;
                texture.height = mipObject.height;
              }
            }
          }
          gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
          gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
          gl.texImage2D(gl.TEXTURE_2D, mipLevel, texture._glInternalFormat, texture._glFormat, texture._glPixelType, mipObject);
        } else {
          gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
          resMult = 1 / Math.pow(2, mipLevel);
          if (texture._compressed) {
            gl.compressedTexImage2D(gl.TEXTURE_2D, mipLevel, texture._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, mipObject);
          } else {
            gl.texImage2D(gl.TEXTURE_2D, mipLevel, texture._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, texture._glFormat, texture._glPixelType, mipObject);
          }
        }
        if (mipLevel === 0) {
          texture._mipmapsUploaded = false;
        } else {
          texture._mipmapsUploaded = true;
        }
      }
      mipLevel++;
    }
    if (texture._needsUpload) {
      if (texture._cubemap) {
        for (var i = 0;i < 6;i++) {
          texture._levelsUpdated[0][i] = false;
        }
      } else {
        texture._levelsUpdated[0] = false;
      }
    }
    if (!texture._compressed && texture._mipmaps && texture._needsMipmapsUpload && texture._pot && texture._levels.length === 1) {
      gl.generateMipmap(texture._glTarget);
      texture._mipmapsUploaded = true;
    }
    if (texture._gpuSize) {
      this._vram.tex -= texture._gpuSize;
    }
    texture._gpuSize = gpuTexSize(gl, texture);
    this._vram.tex += texture._gpuSize;
  }, setTexture:function(texture, textureUnit) {
    var gl = this.gl;
    if (!texture._glTextureId) {
      this.initializeTexture(texture);
    }
    var paramDirty = texture._minFilterDirty || texture._magFilterDirty || texture._addressUDirty || texture._addressVDirty || texture._anisotropyDirty;
    if (this.textureUnits[textureUnit] !== texture || paramDirty) {
      if (this.activeTexture !== textureUnit) {
        gl.activeTexture(gl.TEXTURE0 + textureUnit);
        this.activeTexture = textureUnit;
      }
      gl.bindTexture(texture._glTarget, texture._glTextureId);
      this.textureUnits[textureUnit] = texture;
    }
    if (paramDirty) {
      if (texture._minFilterDirty) {
        var filter = texture._minFilter;
        if (!texture._pot || !texture._mipmaps || texture._compressed && texture._levels.length === 1) {
          if (filter === pc.FILTER_NEAREST_MIPMAP_NEAREST || filter === pc.FILTER_NEAREST_MIPMAP_LINEAR) {
            filter = pc.FILTER_NEAREST;
          } else {
            if (filter === pc.FILTER_LINEAR_MIPMAP_NEAREST || filter === pc.FILTER_LINEAR_MIPMAP_LINEAR) {
              filter = pc.FILTER_LINEAR;
            }
          }
        }
        gl.texParameteri(texture._glTarget, gl.TEXTURE_MIN_FILTER, this.glFilter[filter]);
        texture._minFilterDirty = false;
      }
      if (texture._magFilterDirty) {
        gl.texParameteri(texture._glTarget, gl.TEXTURE_MAG_FILTER, this.glFilter[texture._magFilter]);
        texture._magFilterDirty = false;
      }
      if (texture._addressUDirty) {
        gl.texParameteri(texture._glTarget, gl.TEXTURE_WRAP_S, this.glAddress[texture._pot ? texture._addressU : pc.ADDRESS_CLAMP_TO_EDGE]);
        texture._addressUDirty = false;
      }
      if (texture._addressVDirty) {
        gl.texParameteri(texture._glTarget, gl.TEXTURE_WRAP_T, this.glAddress[texture._pot ? texture._addressV : pc.ADDRESS_CLAMP_TO_EDGE]);
        texture._addressVDirty = false;
      }
      if (texture._anisotropyDirty) {
        var ext = this.extTextureFilterAnisotropic;
        if (ext) {
          gl.texParameterf(texture._glTarget, ext.TEXTURE_MAX_ANISOTROPY_EXT, Math.max(1, Math.min(Math.round(texture._anisotropy), this.maxAnisotropy)));
        }
        texture._anisotropyDirty = false;
      }
    }
    if (texture._needsUpload || texture._needsMipmapsUpload) {
      this.uploadTexture(texture);
      texture._needsUpload = false;
      texture._needsMipmapsUpload = false;
    }
  }, onVertexBufferDeleted:function() {
    this.boundBuffer = null;
    this.indexBuffer = null;
    this.vertexBuffers.length = 0;
    this.vbOffsets.length = 0;
    this.attributesInvalidated = true;
    for (var loc in this.enabledAttributes) {
      this.gl.disableVertexAttribArray(loc);
    }
    this.enabledAttributes = {};
  }, draw:function(primitive, numInstances) {
    var gl = this.gl;
    var i, j, len;
    var sampler, samplerValue, texture, numTextures;
    var uniform, scopeId, uniformVersion, programVersion, locationId;
    var shader = this.shader;
    var samplers = shader.samplers;
    var uniforms = shader.uniforms;
    if (numInstances > 1) {
      this.boundBuffer = null;
      this.attributesInvalidated = true;
    }
    if (this.attributesInvalidated) {
      var attribute, element, vertexBuffer, vbOffset, bufferId;
      var attributes = shader.attributes;
      for (i = 0, len = attributes.length;i < len;i++) {
        attribute = attributes[i];
        element = attribute.scopeId.value;
        if (element !== null) {
          vertexBuffer = this.vertexBuffers[element.stream];
          vbOffset = this.vbOffsets[element.stream] || 0;
          bufferId = vertexBuffer.bufferId;
          if (this.boundBuffer !== bufferId) {
            gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
            this.boundBuffer = bufferId;
          }
          locationId = attribute.locationId;
          if (!this.enabledAttributes[locationId]) {
            gl.enableVertexAttribArray(locationId);
            this.enabledAttributes[locationId] = true;
          }
          gl.vertexAttribPointer(locationId, element.numComponents, this.glType[element.dataType], element.normalize, element.stride, element.offset + vbOffset);
          if (element.stream === 1 && numInstances > 1) {
            if (!this.instancedAttribs[locationId]) {
              this.extInstancing.vertexAttribDivisorANGLE(locationId, 1);
              this.instancedAttribs[locationId] = true;
            }
          } else {
            if (this.instancedAttribs[locationId]) {
              this.extInstancing.vertexAttribDivisorANGLE(locationId, 0);
              this.instancedAttribs[locationId] = false;
            }
          }
        }
      }
      this.attributesInvalidated = false;
    }
    var textureUnit = 0;
    for (i = 0, len = samplers.length;i < len;i++) {
      sampler = samplers[i];
      samplerValue = sampler.scopeId.value;
      if (!samplerValue) {
        continue;
      }
      if (samplerValue instanceof pc.Texture) {
        texture = samplerValue;
        this.setTexture(texture, textureUnit);
        if (sampler.slot !== textureUnit) {
          gl.uniform1i(sampler.locationId, textureUnit);
          sampler.slot = textureUnit;
        }
        textureUnit++;
      } else {
        sampler.array.length = 0;
        numTextures = samplerValue.length;
        for (j = 0;j < numTextures;j++) {
          texture = samplerValue[j];
          this.setTexture(texture, textureUnit);
          sampler.array[j] = textureUnit;
          textureUnit++;
        }
        gl.uniform1iv(sampler.locationId, sampler.array);
      }
    }
    for (i = 0, len = uniforms.length;i < len;i++) {
      uniform = uniforms[i];
      scopeId = uniform.scopeId;
      uniformVersion = uniform.version;
      programVersion = scopeId.versionObject.version;
      if (uniformVersion.globalId !== programVersion.globalId || uniformVersion.revision !== programVersion.revision) {
        uniformVersion.globalId = programVersion.globalId;
        uniformVersion.revision = programVersion.revision;
        if (scopeId.value !== null) {
          this.commitFunction[uniform.dataType](uniform, scopeId.value);
        }
      }
    }
    this._drawCallsPerFrame++;
    this._primsPerFrame[primitive.type] += primitive.count * (numInstances > 1 ? numInstances : 1);
    if (primitive.indexed) {
      if (numInstances > 1) {
        this.extInstancing.drawElementsInstancedANGLE(this.glPrimitive[primitive.type], primitive.count, this.indexBuffer.glFormat, primitive.base * 2, numInstances);
        this.boundBuffer = null;
        this.attributesInvalidated = true;
      } else {
        gl.drawElements(this.glPrimitive[primitive.type], primitive.count, this.indexBuffer.glFormat, primitive.base * this.indexBuffer.bytesPerIndex);
      }
    } else {
      if (numInstances > 1) {
        this.extInstancing.drawArraysInstancedANGLE(this.glPrimitive[primitive.type], primitive.base, primitive.count, numInstances);
        this.boundBuffer = null;
        this.attributesInvalidated = true;
      } else {
        gl.drawArrays(this.glPrimitive[primitive.type], primitive.base, primitive.count);
      }
    }
  }, clear:function(options) {
    var defaultOptions = this.defaultClearOptions;
    options = options || defaultOptions;
    var flags = options.flags == undefined ? defaultOptions.flags : options.flags;
    if (flags !== 0) {
      var gl = this.gl;
      if (flags & pc.CLEARFLAG_COLOR) {
        var color = options.color == undefined ? defaultOptions.color : options.color;
        this.setClearColor(color[0], color[1], color[2], color[3]);
      }
      if (flags & pc.CLEARFLAG_DEPTH) {
        var depth = options.depth == undefined ? defaultOptions.depth : options.depth;
        this.setClearDepth(depth);
        if (!this.depthWrite) {
          gl.depthMask(true);
        }
      }
      if (flags & pc.CLEARFLAG_STENCIL) {
        var stencil = options.stencil == undefined ? defaultOptions.stencil : options.stencil;
        this.setClearStencil(stencil);
      }
      gl.clear(this.glClearFlag[flags]);
      if (flags & pc.CLEARFLAG_DEPTH) {
        if (!this.depthWrite) {
          gl.depthMask(false);
        }
      }
    }
  }, readPixels:function(x, y, w, h, pixels) {
    var gl = this.gl;
    gl.readPixels(x, y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
  }, setClearDepth:function(depth) {
    if (depth !== this.clearDepth) {
      this.gl.clearDepth(depth);
      this.clearDepth = depth;
    }
  }, setClearColor:function(r, g, b, a) {
    if (r !== this.clearRed || g !== this.clearGreen || b !== this.clearBlue || a !== this.clearAlpha) {
      this.gl.clearColor(r, g, b, a);
      this.clearRed = r;
      this.clearGreen = g;
      this.clearBlue = b;
      this.clearAlpha = a;
    }
  }, setClearStencil:function(value) {
    if (value !== this.clearStencil) {
      this.gl.clearStencil(value);
      this.clearStencil = value;
    }
  }, setRenderTarget:function(renderTarget) {
    this.renderTarget = renderTarget;
  }, getRenderTarget:function() {
    return this.renderTarget;
  }, getDepthTest:function() {
    return this.depthTest;
  }, setDepthTest:function(depthTest) {
    if (this.depthTest !== depthTest) {
      var gl = this.gl;
      if (depthTest) {
        gl.enable(gl.DEPTH_TEST);
      } else {
        gl.disable(gl.DEPTH_TEST);
      }
      this.depthTest = depthTest;
    }
  }, getDepthWrite:function() {
    return this.depthWrite;
  }, setDepthWrite:function(writeDepth) {
    if (this.depthWrite !== writeDepth) {
      this.gl.depthMask(writeDepth);
      this.depthWrite = writeDepth;
    }
  }, setColorWrite:function(writeRed, writeGreen, writeBlue, writeAlpha) {
    if (this.writeRed !== writeRed || this.writeGreen !== writeGreen || this.writeBlue !== writeBlue || this.writeAlpha !== writeAlpha) {
      this.gl.colorMask(writeRed, writeGreen, writeBlue, writeAlpha);
      this.writeRed = writeRed;
      this.writeGreen = writeGreen;
      this.writeBlue = writeBlue;
      this.writeAlpha = writeAlpha;
    }
  }, getBlending:function() {
    return this.blending;
  }, setBlending:function(blending) {
    if (this.blending !== blending) {
      var gl = this.gl;
      if (blending) {
        gl.enable(gl.BLEND);
      } else {
        gl.disable(gl.BLEND);
      }
      this.blending = blending;
    }
  }, setStencilTest:function(enable) {
    if (this.stencil !== enable) {
      var gl = this.gl;
      if (enable) {
        gl.enable(gl.STENCIL_TEST);
      } else {
        gl.disable(gl.STENCIL_TEST);
      }
      this.stencil = enable;
    }
  }, setStencilFunc:function(func, ref, mask) {
    if (this.stencilFuncFront !== func || this.stencilRefFront !== ref || this.stencilMaskFront !== mask || this.stencilFuncBack !== func || this.stencilRefBack !== ref || this.stencilMaskBack !== mask) {
      var gl = this.gl;
      gl.stencilFunc(this.glComparison[func], ref, mask);
      this.stencilFuncFront = this.stencilFuncBack = func;
      this.stencilRefFront = this.stencilRefBack = ref;
      this.stencilMaskFront = this.stencilMaskBack = mask;
    }
  }, setStencilFuncFront:function(func, ref, mask) {
    if (this.stencilFuncFront !== func || this.stencilRefFront !== ref || this.stencilMaskFront !== mask) {
      var gl = this.gl;
      gl.stencilFuncSeparate(gl.FRONT, this.glComparison[func], ref, mask);
      this.stencilFuncFront = func;
      this.stencilRefFront = ref;
      this.stencilMaskFront = mask;
    }
  }, setStencilFuncBack:function(func, ref, mask) {
    if (this.stencilFuncBack !== func || this.stencilRefBack !== ref || this.stencilMaskBack !== mask) {
      var gl = this.gl;
      gl.stencilFuncSeparate(gl.BACK, this.glComparison[func], ref, mask);
      this.stencilFuncBack = func;
      this.stencilRefBack = ref;
      this.stencilMaskBack = mask;
    }
  }, setStencilOperation:function(fail, zfail, zpass) {
    if (this.stencilFailFront !== fail || this.stencilZfailFront !== zfail || this.stencilZpassFront !== zpass || this.stencilFailBack !== fail || this.stencilZfailBack !== zfail || this.stencilZpassBack !== zpass) {
      var gl = this.gl;
      gl.stencilOp(this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);
      this.stencilFailFront = this.stencilFailBack = fail;
      this.stencilZfailFront = this.stencilZfailBack = zfail;
      this.stencilZpassFront = this.stencilZpassBack = zpass;
    }
  }, setStencilOperationFront:function(fail, zfail, zpass) {
    if (this.stencilFailFront !== fail || this.stencilZfailFront !== zfail || this.stencilZpassFront !== zpass) {
      var gl = this.gl;
      gl.stencilOpSeparate(gl.FRONT, this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);
      this.stencilFailFront = fail;
      this.stencilZfailFront = zfail;
      this.stencilZpassFront = zpass;
    }
  }, setStencilOperationBack:function(fail, zfail, zpass) {
    if (this.stencilFailBack !== fail || this.stencilZfailBack !== zfail || this.stencilZpassBack !== zpass) {
      var gl = this.gl;
      gl.stencilOpSeparate(gl.BACK, this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);
      this.stencilFailBack = fail;
      this.stencilZfailBack = zfail;
      this.stencilZpassBack = zpass;
    }
  }, setBlendFunction:function(blendSrc, blendDst) {
    if (this.blendSrc !== blendSrc || this.blendDst !== blendDst) {
      this.gl.blendFunc(this.glBlendFunction[blendSrc], this.glBlendFunction[blendDst]);
      this.blendSrc = blendSrc;
      this.blendDst = blendDst;
    }
  }, setBlendEquation:function(blendEquation) {
    if (this.blendEquation !== blendEquation) {
      var gl = this.gl;
      gl.blendEquation(this.glBlendEquation[blendEquation]);
      this.blendEquation = blendEquation;
    }
  }, setCullMode:function(cullMode) {
    if (this.cullMode !== cullMode) {
      if (cullMode === pc.CULLFACE_NONE) {
        this.gl.disable(this.gl.CULL_FACE);
      } else {
        if (this.cullMode === pc.CULLFACE_NONE) {
          this.gl.enable(this.gl.CULL_FACE);
        }
        var mode = this.glCull[cullMode];
        if (this.cullFace !== mode) {
          this.gl.cullFace(mode);
          this.cullFace = mode;
        }
      }
      this.cullMode = cullMode;
    }
  }, getCullMode:function() {
    return this.cullMode;
  }, setIndexBuffer:function(indexBuffer) {
    if (this.indexBuffer !== indexBuffer) {
      this.indexBuffer = indexBuffer;
      var gl = this.gl;
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer ? indexBuffer.bufferId : null);
    }
  }, setVertexBuffer:function(vertexBuffer, stream, vbOffset) {
    if (this.vertexBuffers[stream] !== vertexBuffer || this.vbOffsets[stream] !== vbOffset) {
      this.vertexBuffers[stream] = vertexBuffer;
      this.vbOffsets[stream] = vbOffset;
      var vertexFormat = vertexBuffer.getFormat();
      var i = 0;
      var elements = vertexFormat.elements;
      var numElements = elements.length;
      while (i < numElements) {
        var vertexElement = elements[i++];
        vertexElement.stream = stream;
        vertexElement.scopeId.setValue(vertexElement);
      }
      this.attributesInvalidated = true;
    }
  }, setShader:function(shader) {
    if (shader !== this.shader) {
      this.shader = shader;
      if (!shader.ready) {
        shader.link();
      }
      this._shaderSwitchesPerFrame++;
      this.gl.useProgram(shader.program);
      this.attributesInvalidated = true;
    }
  }, getHdrFormat:function() {
    if (this.extTextureHalfFloatRenderable) {
      return pc.PIXELFORMAT_RGB16F;
    } else {
      if (this.extTextureFloatRenderable) {
        return pc.PIXELFORMAT_RGB32F;
      }
    }
    return pc.PIXELFORMAT_R8_G8_B8_A8;
  }, getBoneLimit:function() {
    return this.boneLimit;
  }, setBoneLimit:function(maxBones) {
    this.boneLimit = maxBones;
  }, enableValidation:function(enable) {
    console.warn("enableValidation: This function is deprecated and will be removed shortly.");
  }, validate:function() {
    console.warn("validate: This function is deprecated and will be removed shortly.");
  }, resizeCanvas:function(width, height) {
    this._width = width;
    this._height = height;
    var ratio = Math.min(this._maxPixelRatio, window.devicePixelRatio);
    width *= ratio;
    height *= ratio;
    this.canvas.width = width;
    this.canvas.height = height;
    this.fire(EVENT_RESIZE, width, height);
  }, setResolution:function(width, height) {
    this._width = width;
    this._height = height;
    this.canvas.width = width;
    this.canvas.height = height;
    this.fire(EVENT_RESIZE, width, height);
  }, clearShaderCache:function() {
    this.programLib.clearCache();
  }, removeShaderFromCache:function(shader) {
    this.programLib.removeFromCache(shader);
  }};
  Object.defineProperty(GraphicsDevice.prototype, "width", {get:function() {
    return this.gl.drawingBufferWidth || this.canvas.width;
  }});
  Object.defineProperty(GraphicsDevice.prototype, "height", {get:function() {
    return this.gl.drawingBufferHeight || this.canvas.height;
  }});
  Object.defineProperty(GraphicsDevice.prototype, "fullscreen", {get:function() {
    return !!document.fullscreenElement;
  }, set:function(fullscreen) {
    if (fullscreen) {
      var canvas = this.gl.canvas;
      canvas.requestFullscreen();
    } else {
      document.exitFullscreen();
    }
  }});
  Object.defineProperty(GraphicsDevice.prototype, "enableAutoInstancing", {get:function() {
    return this._enableAutoInstancing;
  }, set:function(value) {
    this._enableAutoInstancing = value && this.extInstancing;
  }});
  Object.defineProperty(GraphicsDevice.prototype, "maxAnisotropy", {get:function() {
    var maxAniso;
    return function() {
      if (maxAniso === undefined) {
        maxAniso = 1;
        var gl = this.gl;
        var glExt = this.extTextureFilterAnisotropic;
        if (glExt) {
          maxAniso = gl.getParameter(glExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
        }
      }
      return maxAniso;
    };
  }()});
  Object.defineProperty(GraphicsDevice.prototype, "maxPixelRatio", {get:function() {
    return this._maxPixelRatio;
  }, set:function(ratio) {
    this._maxPixelRatio = ratio;
    this.resizeCanvas(this._width, this._height);
  }});
  return {UnsupportedBrowserError:UnsupportedBrowserError, ContextCreationError:ContextCreationError, GraphicsDevice:GraphicsDevice, precalculatedTangents:true};
}());
pc.extend(pc, function() {
  var shaderChunks = {};
  var attrib2Semantic = {};
  attrib2Semantic["vertex_position"] = pc.SEMANTIC_POSITION;
  attrib2Semantic["vertex_normal"] = pc.SEMANTIC_NORMAL;
  attrib2Semantic["vertex_tangent"] = pc.SEMANTIC_TANGENT;
  attrib2Semantic["vertex_texCoord0"] = pc.SEMANTIC_TEXCOORD0;
  attrib2Semantic["vertex_texCoord1"] = pc.SEMANTIC_TEXCOORD1;
  attrib2Semantic["vertex_color"] = pc.SEMANTIC_COLOR;
  shaderChunks.collectAttribs = function(vsCode) {
    var attribs = {};
    var attrs = 0;
    var found = vsCode.indexOf("attribute");
    while (found >= 0) {
      var endOfLine = vsCode.indexOf(";", found);
      var startOfAttribName = vsCode.lastIndexOf(" ", endOfLine);
      var attribName = vsCode.substr(startOfAttribName + 1, endOfLine - (startOfAttribName + 1));
      var semantic = attrib2Semantic[attribName];
      if (semantic !== undefined) {
        attribs[attribName] = semantic;
      } else {
        attribs[attribName] = "ATTR" + attrs;
        attrs++;
      }
      found = vsCode.indexOf("attribute", found + 1);
    }
    return attribs;
  };
  shaderChunks.createShader = function(device, vsName, psName) {
    var vsCode = shaderChunks[vsName];
    var psCode = pc.programlib.precisionCode(device) + "\n" + shaderChunks[psName];
    var attribs = this.collectAttribs(vsCode);
    return new pc.Shader(device, {attributes:attribs, vshader:vsCode, fshader:psCode});
  };
  shaderChunks.createShaderFromCode = function(device, vsCode, psCode, uName) {
    var shaderCache = device.programLib._cache;
    var cached = shaderCache[uName];
    if (cached !== undefined) {
      return cached;
    }
    psCode = pc.programlib.precisionCode(device) + "\n" + psCode;
    var attribs = this.collectAttribs(vsCode);
    shaderCache[uName] = new pc.Shader(device, {attributes:attribs, vshader:vsCode, fshader:psCode});
    return shaderCache[uName];
  };
  return {shaderChunks:shaderChunks};
}());
pc.extend(pc, function() {
  var _postEffectQuadVB = null;
  function drawQuadWithShader(device, target, shader, rect, scissorRect, useBlend) {
    if (_postEffectQuadVB === null) {
      var vertexFormat = new pc.VertexFormat(device, [{semantic:pc.SEMANTIC_POSITION, components:2, type:pc.ELEMENTTYPE_FLOAT32}]);
      _postEffectQuadVB = new pc.VertexBuffer(device, vertexFormat, 4);
      var iterator = new pc.VertexIterator(_postEffectQuadVB);
      iterator.element[pc.SEMANTIC_POSITION].set(-1, -1);
      iterator.next();
      iterator.element[pc.SEMANTIC_POSITION].set(1, -1);
      iterator.next();
      iterator.element[pc.SEMANTIC_POSITION].set(-1, 1);
      iterator.next();
      iterator.element[pc.SEMANTIC_POSITION].set(1, 1);
      iterator.end();
    }
    device.setRenderTarget(target);
    device.updateBegin();
    var x, y, w, h;
    var sx, sy, sw, sh;
    if (!rect) {
      w = target !== null ? target.width : device.width;
      h = target !== null ? target.height : device.height;
      x = 0;
      y = 0;
    } else {
      x = rect.x;
      y = rect.y;
      w = rect.z;
      h = rect.w;
    }
    if (!scissorRect) {
      sx = x;
      sy = y;
      sw = w;
      sh = h;
    } else {
      sx = scissorRect.x;
      sy = scissorRect.y;
      sw = scissorRect.z;
      sh = scissorRect.w;
    }
    device.setViewport(x, y, w, h);
    device.setScissor(sx, sy, sw, sh);
    var oldDepthTest = device.getDepthTest();
    var oldDepthWrite = device.getDepthWrite();
    device.setDepthTest(false);
    device.setDepthWrite(false);
    if (!useBlend) {
      device.setBlending(false);
    }
    device.setVertexBuffer(_postEffectQuadVB, 0);
    device.setShader(shader);
    device.draw({type:pc.PRIMITIVE_TRISTRIP, base:0, count:4, indexed:false});
    device.setDepthTest(oldDepthTest);
    device.setDepthWrite(oldDepthWrite);
    device.updateEnd();
  }
  function destroyPostEffectQuad() {
    _postEffectQuadVB = null;
  }
  return {drawQuadWithShader:drawQuadWithShader, destroyPostEffectQuad:destroyPostEffectQuad};
}());
pc.extend(pc, function() {
  function fixChrome() {
    var endTime = Date.now() + 10;
    while (Date.now() < endTime) {
    }
  }
  function syncToCpu(device, targ, face) {
    var tex = targ._colorBuffer;
    if (tex.format != pc.PIXELFORMAT_R8_G8_B8_A8) {
      return;
    }
    var pixels = new Uint8Array(tex.width * tex.height * 4);
    var gl = device.gl;
    gl.bindFramebuffer(gl.FRAMEBUFFER, targ._glFrameBuffer);
    gl.readPixels(0, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
    if (!tex._levels) {
      tex._levels = [];
    }
    if (!tex._levels[0]) {
      tex._levels[0] = [];
    }
    tex._levels[0][face] = pixels;
  }
  function prefilterCubemap(options) {
    var device = options.device;
    var sourceCubemap = options.sourceCubemap;
    var method = options.method;
    var samples = options.samples;
    var cpuSync = options.cpuSync;
    var chromeFix = options.chromeFix;
    if (cpuSync && !sourceCubemap._levels[0]) {
      console.error("ERROR: prefilter: cubemap must have _levels");
      return;
    }
    var chunks = pc.shaderChunks;
    var rgbmSource = sourceCubemap.rgbm;
    var shader = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, chunks.rgbmPS + chunks.prefilterCubemapPS.replace(/\$METHOD/g, method === 0 ? "cos" : "phong").replace(/\$NUMSAMPLES/g, samples).replace(/\$textureCube/g, rgbmSource ? "textureCubeRGBM" : "textureCube"), "prefilter" + method + "" + samples + "" + rgbmSource);
    var shader2 = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, chunks.outputCubemapPS, "outputCubemap");
    var constantTexSource = device.scope.resolve("source");
    var constantParams = device.scope.resolve("params");
    var params = new pc.Vec4;
    var size = sourceCubemap.width;
    var format = sourceCubemap.format;
    var cmapsList = [[], options.filteredFixed, options.filteredRgbm, options.filteredFixedRgbm];
    var gloss = method === 0 ? [.9, .85, .7, .4, .25] : [512, 128, 32, 8, 2];
    var mipSize = [64, 32, 16, 8, 4];
    var numMips = 5;
    var targ;
    var i, face, pass;
    var rgbFormat = format === pc.PIXELFORMAT_R8_G8_B8;
    var isImg = false;
    var nextCubemap, cubemap;
    if (cpuSync) {
      isImg = sourceCubemap._levels[0][0] instanceof HTMLImageElement;
    }
    if ((rgbFormat || isImg) && cpuSync) {
      format = pc.PIXELFORMAT_R8_G8_B8_A8;
      nextCubemap = new pc.gfx.Texture(device, {cubemap:true, rgbm:rgbmSource, format:format, width:size, height:size, mipmaps:false});
      for (face = 0;face < 6;face++) {
        targ = new pc.RenderTarget(device, nextCubemap, {face:face, depth:false});
        params.x = face;
        params.y = 0;
        constantTexSource.setValue(sourceCubemap);
        constantParams.setValue(params.data);
        if (chromeFix) {
          fixChrome();
        }
        pc.drawQuadWithShader(device, targ, shader2);
        syncToCpu(device, targ, face);
      }
      sourceCubemap = nextCubemap;
    }
    if (size > 128) {
      var log128 = Math.round(Math.log2(128));
      var logSize = Math.round(Math.log2(size));
      var steps = logSize - log128;
      for (i = 0;i < steps;i++) {
        size = sourceCubemap.width * .5;
        var sampleGloss = method === 0 ? 1 : Math.pow(2, Math.round(Math.log2(gloss[0]) + (steps - i) * 2));
        nextCubemap = new pc.gfx.Texture(device, {cubemap:true, rgbm:rgbmSource, format:format, width:size, height:size, mipmaps:false});
        for (face = 0;face < 6;face++) {
          targ = new pc.RenderTarget(device, nextCubemap, {face:face, depth:false});
          params.x = face;
          params.y = sampleGloss;
          params.z = size;
          params.w = rgbmSource ? 3 : 0;
          constantTexSource.setValue(sourceCubemap);
          constantParams.setValue(params.data);
          if (chromeFix) {
            fixChrome();
          }
          pc.drawQuadWithShader(device, targ, shader2);
          if (i === steps - 1 && cpuSync) {
            syncToCpu(device, targ, face);
          }
        }
        sourceCubemap = nextCubemap;
      }
    }
    options.sourceCubemap = sourceCubemap;
    var sourceCubemapRgbm = null;
    if (!rgbmSource && options.filteredFixedRgbm) {
      nextCubemap = new pc.gfx.Texture(device, {cubemap:true, rgbm:true, format:pc.PIXELFORMAT_R8_G8_B8_A8, width:size, height:size, mipmaps:false});
      for (face = 0;face < 6;face++) {
        targ = new pc.RenderTarget(device, nextCubemap, {face:face, depth:false});
        params.x = face;
        params.w = 2;
        constantTexSource.setValue(sourceCubemap);
        constantParams.setValue(params.data);
        if (chromeFix) {
          fixChrome();
        }
        pc.drawQuadWithShader(device, targ, shader2);
        syncToCpu(device, targ, face);
      }
      sourceCubemapRgbm = nextCubemap;
    }
    var unblurredGloss = method === 0 ? 1 : 2048;
    var startPass = method === 0 ? 0 : -1;
    cmapsList[startPass] = [];
    for (i = 0;i < numMips;i++) {
      for (pass = startPass;pass < cmapsList.length;pass++) {
        if (cmapsList[pass] != null) {
          cmapsList[pass][i] = new pc.gfx.Texture(device, {cubemap:true, rgbm:pass < 2 ? rgbmSource : true, format:pass < 2 ? format : pc.PIXELFORMAT_R8_G8_B8_A8, fixCubemapSeams:pass === 1 || pass === 3, width:mipSize[i], height:mipSize[i], mipmaps:false});
        }
      }
    }
    for (pass = startPass;pass < cmapsList.length;pass++) {
      if (cmapsList[pass] != null) {
        if (pass > 1 && rgbmSource) {
          cmapsList[pass] = cmapsList[pass - 2];
          continue;
        }
        for (i = 0;i < numMips;i++) {
          for (face = 0;face < 6;face++) {
            targ = new pc.RenderTarget(device, cmapsList[pass][i], {face:face, depth:false});
            params.x = face;
            params.y = pass < 0 ? unblurredGloss : gloss[i];
            params.z = mipSize[i];
            params.w = rgbmSource ? 3 : pass;
            constantTexSource.setValue(i === 0 ? sourceCubemap : method === 0 ? cmapsList[0][i - 1] : cmapsList[-1][i - 1]);
            constantParams.setValue(params.data);
            if (chromeFix) {
              fixChrome();
            }
            pc.drawQuadWithShader(device, targ, shader);
            if (cpuSync) {
              syncToCpu(device, targ, face);
            }
          }
        }
      }
    }
    options.filtered = cmapsList[0];
    var mips;
    if (cpuSync && options.singleFilteredFixed) {
      mips = [sourceCubemap, options.filteredFixed[0], options.filteredFixed[1], options.filteredFixed[2], options.filteredFixed[3], options.filteredFixed[4], options.filteredFixed[5]];
      cubemap = new pc.gfx.Texture(device, {cubemap:true, rgbm:rgbmSource, fixCubemapSeams:true, format:format, width:128, height:128, addressU:pc.ADDRESS_CLAMP_TO_EDGE, addressV:pc.ADDRESS_CLAMP_TO_EDGE});
      for (i = 0;i < 6;i++) {
        cubemap._levels[i] = mips[i]._levels[0];
      }
      cubemap.upload();
      cubemap._prefilteredMips = true;
      options.singleFilteredFixed = cubemap;
    }
    if (cpuSync && options.singleFilteredFixedRgbm && options.filteredFixedRgbm) {
      mips = [sourceCubemapRgbm, options.filteredFixedRgbm[0], options.filteredFixedRgbm[1], options.filteredFixedRgbm[2], options.filteredFixedRgbm[3], options.filteredFixedRgbm[4], options.filteredFixedRgbm[5]];
      cubemap = new pc.gfx.Texture(device, {cubemap:true, rgbm:true, fixCubemapSeams:true, format:pc.PIXELFORMAT_R8_G8_B8_A8, width:128, height:128, addressU:pc.ADDRESS_CLAMP_TO_EDGE, addressV:pc.ADDRESS_CLAMP_TO_EDGE});
      for (i = 0;i < 6;i++) {
        cubemap._levels[i] = mips[i]._levels[0];
      }
      cubemap.upload();
      cubemap._prefilteredMips = true;
      options.singleFilteredFixedRgbm = cubemap;
    }
  }
  function areaElement(x, y) {
    return Math.atan2(x * y, Math.sqrt(x * x + y * y + 1));
  }
  function texelCoordSolidAngle(u, v, size) {
    var _u = 2 * (u + .5) / size - 1;
    var _v = 2 * (v + .5) / size - 1;
    _u *= 1 - 1 / size;
    _v *= 1 - 1 / size;
    var invResolution = 1 / size;
    var x0 = _u - invResolution;
    var y0 = _v - invResolution;
    var x1 = _u + invResolution;
    var y1 = _v + invResolution;
    var solidAngle = areaElement(x0, y0) - areaElement(x0, y1) - areaElement(x1, y0) + areaElement(x1, y1);
    if (u === 0 && v === 0 || u === size - 1 && v === 0 || u === 0 && v === size - 1 || u === size - 1 && v === size - 1) {
      solidAngle /= 3;
    } else {
      if (u === 0 || v === 0 || u === size - 1 || v === size - 1) {
        solidAngle *= .5;
      }
    }
    return solidAngle;
  }
  function shFromCubemap(source, dontFlipX) {
    var face;
    var cubeSize = source.width;
    var x, y;
    if (source.format != pc.PIXELFORMAT_R8_G8_B8_A8) {
      console.error("ERROR: SH: cubemap must be RGBA8");
      return;
    }
    if (!source._levels[0]) {
      console.error("ERROR: SH: cubemap must be synced to CPU");
      return;
    }
    if (!source._levels[0][0].length) {
      if (source._levels[0][0] instanceof HTMLImageElement) {
        var device = pc.Application.getApplication().graphicsDevice;
        var gl = device.gl;
        var chunks = pc.shaderChunks;
        var shader = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, chunks.fullscreenQuadPS, "fsQuadSimple");
        var constantTexSource = device.scope.resolve("source");
        for (face = 0;face < 6;face++) {
          var img = source._levels[0][face];
          var tex = new pc.Texture(device, {cubemap:false, rgbm:false, format:source.format, width:cubeSize, height:cubeSize, mipmaps:false});
          tex._levels[0] = img;
          tex.upload();
          var tex2 = new pc.Texture(device, {cubemap:false, rgbm:false, format:source.format, width:cubeSize, height:cubeSize, mipmaps:false});
          var targ = new pc.RenderTarget(device, tex2, {depth:false});
          constantTexSource.setValue(tex);
          pc.drawQuadWithShader(device, targ, shader);
          var pixels = new Uint8Array(cubeSize * cubeSize * 4);
          gl.bindFramebuffer(gl.FRAMEBUFFER, targ._glFrameBuffer);
          gl.readPixels(0, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
          source._levels[0][face] = pixels;
        }
      } else {
        console.error("ERROR: SH: cubemap must be composed of arrays or images");
        return;
      }
    }
    var dirs = [];
    for (y = 0;y < cubeSize;y++) {
      for (x = 0;x < cubeSize;x++) {
        var u = x / (cubeSize - 1) * 2 - 1;
        var v = y / (cubeSize - 1) * 2 - 1;
        dirs[y * cubeSize + x] = (new pc.Vec3(u, v, 1)).normalize();
      }
    }
    var sh = new Float32Array(9 * 3);
    var coef1 = 0;
    var coef2 = 1 * 3;
    var coef3 = 2 * 3;
    var coef4 = 3 * 3;
    var coef5 = 4 * 3;
    var coef6 = 5 * 3;
    var coef7 = 6 * 3;
    var coef8 = 7 * 3;
    var coef9 = 8 * 3;
    var nx = 0;
    var px = 1;
    var ny = 2;
    var py = 3;
    var nz = 4;
    var pz = 5;
    var addr, c, a, value, weight, dir, dx, dy, dz;
    var weight1, weight2, weight3, weight4, weight5;
    var accum = 0;
    for (face = 0;face < 6;face++) {
      for (y = 0;y < cubeSize;y++) {
        for (x = 0;x < cubeSize;x++) {
          addr = y * cubeSize + x;
          weight = texelCoordSolidAngle(x, y, cubeSize);
          weight1 = weight * 4 / 17;
          weight2 = weight * 8 / 17;
          weight3 = weight * 15 / 17;
          weight4 = weight * 5 / 68;
          weight5 = weight * 15 / 68;
          dir = dirs[addr];
          if (face == nx) {
            dx = dir.z;
            dy = -dir.y;
            dz = -dir.x;
          } else {
            if (face == px) {
              dx = -dir.z;
              dy = -dir.y;
              dz = dir.x;
            } else {
              if (face == ny) {
                dx = dir.x;
                dy = dir.z;
                dz = dir.y;
              } else {
                if (face == py) {
                  dx = dir.x;
                  dy = -dir.z;
                  dz = -dir.y;
                } else {
                  if (face == nz) {
                    dx = dir.x;
                    dy = -dir.y;
                    dz = dir.z;
                  } else {
                    if (face == pz) {
                      dx = -dir.x;
                      dy = -dir.y;
                      dz = -dir.z;
                    }
                  }
                }
              }
            }
          }
          if (!dontFlipX) {
            dx = -dx;
          }
          a = source._levels[0][face][addr * 4 + 3] / 255;
          for (c = 0;c < 3;c++) {
            value = source._levels[0][face][addr * 4 + c] / 255;
            if (source.rgbm) {
              value *= a * 8;
              value *= value;
            } else {
              value = Math.pow(value, 2.2);
            }
            sh[coef1 + c] += value * weight1;
            sh[coef2 + c] += value * weight2 * dx;
            sh[coef3 + c] += value * weight2 * dy;
            sh[coef4 + c] += value * weight2 * dz;
            sh[coef5 + c] += value * weight3 * dx * dz;
            sh[coef6 + c] += value * weight3 * dz * dy;
            sh[coef7 + c] += value * weight3 * dy * dx;
            sh[coef8 + c] += value * weight4 * (3 * dz * dz - 1);
            sh[coef9 + c] += value * weight5 * (dx * dx - dy * dy);
            accum += weight;
          }
        }
      }
    }
    for (c = 0;c < sh.length;c++) {
      sh[c] *= 4 * Math.PI / accum;
    }
    return sh;
  }
  return {prefilterCubemap:prefilterCubemap, shFromCubemap:shFromCubemap};
}());
pc.extend(pc, function() {
  var dpMult = 2;
  function paraboloidFromCubemap(device, sourceCubemap, fixSeamsAmount, dontFlipX) {
    var chunks = pc.shaderChunks;
    var shader = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, (sourceCubemap.fixCubemapSeams ? chunks.fixCubemapSeamsStretchPS : chunks.fixCubemapSeamsNonePS) + chunks.genParaboloidPS, "genParaboloid");
    var constantTexSource = device.scope.resolve("source");
    var constantParams = device.scope.resolve("params");
    var params = new pc.Vec4;
    var size = sourceCubemap.width;
    var rgbmSource = sourceCubemap.rgbm;
    var format = sourceCubemap.format;
    size = Math.max(size, 8) * dpMult;
    var tex = new pc.gfx.Texture(device, {rgbm:rgbmSource, format:format, width:size * 2, height:size, mipmaps:false});
    var targ = new pc.RenderTarget(device, tex, {depth:false});
    params.x = fixSeamsAmount;
    params.y = dontFlipX ? -1 : 1;
    constantTexSource.setValue(sourceCubemap);
    constantParams.setValue(params.data);
    pc.drawQuadWithShader(device, targ, shader);
    return tex;
  }
  function getDpAtlasRect(rect, mip) {
    rect.x = pc.math.clamp(mip - 2, 0, 1) * .5;
    var t = mip - rect.x * 6;
    var i = 1 - rect.x;
    rect.y = Math.min(t * .5, .75) * i + rect.x;
    rect.z = (1 - pc.math.clamp(t, 0, 1) * .5) * i;
    rect.w = rect.z * .5;
    return 1 / rect.z;
  }
  function generateDpAtlas(device, sixCubemaps, dontFlipX) {
    var dp, rect;
    rect = new pc.Vec4;
    var params = new pc.Vec4;
    var size = sixCubemaps[0].width * 2 * dpMult;
    var chunks = pc.shaderChunks;
    var shader = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, chunks.dpAtlasQuadPS, "dpAtlasQuad");
    var constantTexSource = device.scope.resolve("source");
    var constantParams = device.scope.resolve("params");
    var tex = new pc.gfx.Texture(device, {rgbm:sixCubemaps[0].rgbm, format:sixCubemaps[0].format, width:size, height:size, mipmaps:false});
    var targ = new pc.RenderTarget(device, tex, {depth:false});
    var borderSize = 2;
    var mip0Width = size;
    var scaleFactor = (mip0Width + borderSize) / mip0Width - 1;
    var scaleAmount;
    for (var i = 0;i < 6;i++) {
      dp = pc.paraboloidFromCubemap(device, sixCubemaps[i], i, dontFlipX);
      constantTexSource.setValue(dp);
      scaleAmount = getDpAtlasRect(rect, i);
      params.x = scaleAmount * scaleFactor;
      params.y = params.x * 2;
      params.x += 1;
      params.y += 1;
      constantParams.setValue(params.data);
      rect.x *= size;
      rect.y *= size;
      rect.z *= size;
      rect.w *= size;
      pc.drawQuadWithShader(device, targ, shader, rect);
    }
    return tex;
  }
  return {paraboloidFromCubemap:paraboloidFromCubemap, generateDpAtlas:generateDpAtlas};
}());
pc.shaderChunks.TBNPS = "void getTBN() {\n    dTBN = mat3(normalize(vTangentW), normalize(vBinormalW), normalize(vNormalW));\n}\n";
pc.shaderChunks.TBNfastPS = "void getTBN() {\n    dTBN = mat3((vTangentW), (vBinormalW), (vNormalW));\n}\n";
pc.shaderChunks.alphaTestPS = "uniform float alpha_ref;\nvoid alphaTest(float a) {\n    if (a < alpha_ref) discard;\n}\n";
pc.shaderChunks.ambientConstantPS = "\nvoid addAmbient() {\n    dDiffuseLight += light_globalAmbient;\n}\n";
pc.shaderChunks.ambientPrefilteredCubePS = "void addAmbient() {\n    vec3 fixedReflDir = fixSeamsStatic(dNormalW, 1.0 - 1.0 / 4.0);\n    fixedReflDir.x *= -1.0;\n    dDiffuseLight += processEnvironment($DECODE(textureCube(texture_prefilteredCubeMap4, fixedReflDir)).rgb);\n}\n";
pc.shaderChunks.ambientPrefilteredCubeLodPS = "void addAmbient() {\n    vec3 fixedReflDir = fixSeamsStatic(dNormalW, 1.0 - 1.0 / 4.0);\n    fixedReflDir.x *= -1.0;\n    dDiffuseLight += processEnvironment($DECODE( textureCubeLodEXT(texture_prefilteredCubeMap128, fixedReflDir, 5.0) ).rgb);\n}\n";
pc.shaderChunks.ambientSHPS = "uniform vec3 ambientSH[9];\nvoid addAmbient() {\n    vec3 n = dNormalW;\n    vec3 color =\n                        ambientSH[0] +\n                        ambientSH[1] * n.x +\n                        ambientSH[2] * n.y +\n                        ambientSH[3] * n.z +\n                        ambientSH[4] * n.x * n.z +\n                        ambientSH[5] * n.z * n.y +\n                        ambientSH[6] * n.y * n.x +\n                        ambientSH[7] * (3.0 * n.z * n.z - 1.0) +\n                        ambientSH[8] * (n.x * n.x - n.y * n.y);\n    dDiffuseLight += processEnvironment(max(color, vec3(0.0)));\n}\n";
pc.shaderChunks.aoSpecOccPS = "uniform float material_occludeSpecularIntensity;\nvoid occludeSpecular() {\n    // approximated specular occlusion from AO\n    float specPow = exp2(dGlossiness * 11.0);\n    // http://research.tri-ace.com/Data/cedec2011_RealtimePBR_Implementation_e.pptx\n    float specOcc = saturate(pow(dot(dNormalW, dViewDirW) + dAo, 0.01*specPow) - 1.0 + dAo);\n    specOcc = mix(1.0, specOcc, material_occludeSpecularIntensity);\n    dSpecularLight *= specOcc;\n    dReflection *= specOcc;\n}\n";
pc.shaderChunks.aoSpecOccConstPS = "void occludeSpecular() {\n    // approximated specular occlusion from AO\n    float specPow = exp2(dGlossiness * 11.0);\n    // http://research.tri-ace.com/Data/cedec2011_RealtimePBR_Implementation_e.pptx\n    float specOcc = saturate(pow(dot(dNormalW, dViewDirW) + dAo, 0.01*specPow) - 1.0 + dAo);\n    dSpecularLight *= specOcc;\n    dReflection *= specOcc;\n}\n";
pc.shaderChunks.aoSpecOccConstSimplePS = "void occludeSpecular() {\n    float specOcc = dAo;\n    dSpecularLight *= specOcc;\n    dReflection *= specOcc;\n}\n";
pc.shaderChunks.aoSpecOccSimplePS = "uniform float material_occludeSpecularIntensity;\nvoid occludeSpecular() {\n    float specOcc = mix(1.0, dAo, material_occludeSpecularIntensity);\n    dSpecularLight *= specOcc;\n    dReflection *= specOcc;\n}\n";
pc.shaderChunks.aoTexPS = "uniform sampler2D texture_aoMap;\nvoid applyAO() {\n    dAo = texture2D(texture_aoMap, $UV).$CH;\n    dDiffuseLight *= dAo;\n}\n";
pc.shaderChunks.aoVertPS = "void applyAO() {\n    dAo = saturate(vVertexColor.$CH);\n    dDiffuseLight *= dAo;\n}\n";
pc.shaderChunks.bakeDirLmEndPS = "\n    vec4 dirLm = texture2D(texture_dirLightMap, vUv1);\n    if (bakeDir > 0.5) {\n        if (dAtten > 0.00001) {\n            dirLm.xyz = dirLm.xyz * 2.0 - vec3(1.0);\n            dAtten = saturate(dAtten);\n            gl_FragColor.rgb = normalize(dLightDirNormW.xyz*dAtten + dirLm.xyz*dirLm.w) * 0.5 + vec3(0.5);\n            gl_FragColor.a = dirLm.w + dAtten;\n            gl_FragColor.a = max(gl_FragColor.a, 1.0 / 255.0);\n        } else {\n            gl_FragColor = dirLm;\n        }\n    } else {\n        gl_FragColor.rgb = dirLm.xyz;\n        gl_FragColor.a = max(dirLm.w, dAtten > 0.00001? (1.0/255.0) : 0.0);\n    }\n";
pc.shaderChunks.bakeLmEndPS = "\ngl_FragColor.rgb = dDiffuseLight;\ngl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(0.5));\ngl_FragColor.rgb /= 8.0;\ngl_FragColor.a = clamp( max( max( gl_FragColor.r, gl_FragColor.g ), max( gl_FragColor.b, 1.0 / 255.0 ) ), 0.0,1.0 );\ngl_FragColor.a = ceil(gl_FragColor.a * 255.0) / 255.0;\ngl_FragColor.rgb /= gl_FragColor.a;\n";
pc.shaderChunks.basePS = "\nuniform vec3 view_position;\nuniform vec3 light_globalAmbient;\nfloat square(float x) {\n    return x*x;\n}\nfloat saturate(float x) {\n    return clamp(x, 0.0, 1.0);\n}\nvec3 saturate(vec3 x) {\n    return clamp(x, vec3(0.0), vec3(1.0));\n}\n";
pc.shaderChunks.baseVS = "\nattribute vec3 vertex_position;\nattribute vec3 vertex_normal;\nattribute vec4 vertex_tangent;\nattribute vec2 vertex_texCoord0;\nattribute vec2 vertex_texCoord1;\nattribute vec4 vertex_color;\nuniform mat4 matrix_viewProjection;\nuniform mat4 matrix_model;\nuniform mat3 matrix_normal;\nvec3 dPositionW;\nmat4 dModelMatrix;\nmat3 dNormalMatrix;\nvec3 dLightPosW;\nvec3 dLightDirNormW;\nvec3 dNormalW;\n";
pc.shaderChunks.biasConstPS = "float getShadowBias(float resolution, float maxBias) {\n    return maxBias;\n}\n";
pc.shaderChunks.biasRcvPlanePS = "vec2 computeReceiverPlaneDepthBias(vec3 texCoordDX, vec3 texCoordDY) {\n    vec2 biasUV;\n    biasUV.x = texCoordDY.y * texCoordDX.z - texCoordDX.y * texCoordDY.z;\n    biasUV.y = texCoordDX.x * texCoordDY.z - texCoordDY.x * texCoordDX.z;\n    biasUV *= 1.0 / ((texCoordDX.x * texCoordDY.y) - (texCoordDX.y * texCoordDY.x));\n    return biasUV;\n}\nfloat getShadowBias(float resolution, float maxBias) {\n    vec3 shadowPosDX = dFdx(dShadowCoord);\n    vec3 shadowPosDY = dFdy(dShadowCoord);\n    vec2 texelSize = vec2(1.0 / resolution);\n    vec2 receiverPlaneDepthBias = computeReceiverPlaneDepthBias(shadowPosDX, shadowPosDY);\n    float fractionalSamplingError = 2.0 * dot(vec2(texelSize), abs(receiverPlaneDepthBias));\n    return -min(fractionalSamplingError, maxBias);\n}\n";
pc.shaderChunks.blurVSMPS = "\nvarying vec2 vUv0;\nuniform sampler2D source;\nuniform vec2 pixelOffset;\n#ifdef GAUSS\nuniform float weight[SAMPLES];\n#endif\n#ifdef PACKED\nfloat decodeFloatRG(vec2 rg) {\n    return rg.y*(1.0/255.0) + rg.x;\n}\nvec2 encodeFloatRG( float v ) {\n  vec2 enc = vec2(1.0, 255.0) * v;\n  enc = fract(enc);\n  enc -= enc.yy * vec2(1.0/255.0, 1.0/255.0);\n  return enc;\n}\n#endif\nvoid main(void) {\n    vec3 moments = vec3(0.0);\n    vec2 uv = vUv0 - pixelOffset * (float(SAMPLES) * 0.5);\n    for(int i=0; i<SAMPLES; i++) {\n        vec4 c = texture2D(source, uv + pixelOffset * float(i));\n        #ifdef PACKED\n        c.xy = vec2(decodeFloatRG(c.xy), decodeFloatRG(c.zw));\n        #endif\n        #ifdef GAUSS\n        moments += c.xyz * weight[i];\n        #else\n        moments += c.xyz;\n        #endif\n    }\n    #ifndef GAUSS\n    moments /= float(SAMPLES);\n    #endif\n    #ifdef PACKED\n    gl_FragColor = vec4(encodeFloatRG(moments.x), encodeFloatRG(moments.y));\n    #else\n    gl_FragColor = vec4(moments.x, moments.y, moments.z, 1.0);\n    #endif\n}\n";
pc.shaderChunks.combineDiffusePS = "vec3 combineColor() {\n    return dAlbedo * dDiffuseLight;\n}\n";
pc.shaderChunks.combineDiffuseSpecularPS = "vec3 combineColor() {\n    return mix(dAlbedo * dDiffuseLight, dSpecularLight + dReflection.rgb * dReflection.a, dSpecularity);\n}\n";
pc.shaderChunks.combineDiffuseSpecularNoConservePS = "vec3 combineColor() {\n    return dAlbedo * dDiffuseLight + (dSpecularLight + dReflection.rgb * dReflection.a) * dSpecularity;\n}\n";
pc.shaderChunks.combineDiffuseSpecularNoReflPS = "vec3 combineColor() {\n    return dAlbedo * dDiffuseLight + dSpecularLight * dSpecularity;\n}\n";
pc.shaderChunks.combineDiffuseSpecularNoReflSeparateAmbientPS = "uniform vec3 material_ambient;\nvec3 combineColor() {\n    return (dDiffuseLight - light_globalAmbient) * dAlbedo + dSpecularLight * dSpecularity + material_ambient * light_globalAmbient;\n}\n";
pc.shaderChunks.combineDiffuseSpecularOldPS = "vec3 combineColor() {\n    return mix(dAlbedo * dDiffuseLight + dSpecularLight * dSpecularity, dReflection.rgb, dReflection.a);\n}\n";
pc.shaderChunks.cookiePS = "vec4 getCookie2D(sampler2D tex, mat4 transform, float intensity) {\n    vec4 projPos = transform * vec4(vPositionW, 1.0);\n    projPos.xy /= projPos.w;\n    return mix(vec4(1.0), texture2D(tex, projPos.xy), intensity);\n}\nvec4 getCookie2DClip(sampler2D tex, mat4 transform, float intensity) {\n    vec4 projPos = transform * vec4(vPositionW, 1.0);\n    projPos.xy /= projPos.w;\n    if (projPos.x < 0.0 || projPos.x > 1.0 || projPos.y < 0.0 || projPos.y > 1.0 || projPos.z < 0.0) return vec4(0.0);\n    return mix(vec4(1.0), texture2D(tex, projPos.xy), intensity);\n}\nvec4 getCookie2DXform(sampler2D tex, mat4 transform, float intensity, vec4 cookieMatrix, vec2 cookieOffset) {\n    vec4 projPos = transform * vec4(vPositionW, 1.0);\n    projPos.xy /= projPos.w;\n    projPos.xy += cookieOffset;\n    vec2 uv = mat2(cookieMatrix) * (projPos.xy-vec2(0.5)) + vec2(0.5);\n    return mix(vec4(1.0), texture2D(tex, uv), intensity);\n}\nvec4 getCookie2DClipXform(sampler2D tex, mat4 transform, float intensity, vec4 cookieMatrix, vec2 cookieOffset) {\n    vec4 projPos = transform * vec4(vPositionW, 1.0);\n    projPos.xy /= projPos.w;\n    projPos.xy += cookieOffset;\n    if (projPos.x < 0.0 || projPos.x > 1.0 || projPos.y < 0.0 || projPos.y > 1.0 || projPos.z < 0.0) return vec4(0.0);\n    vec2 uv = mat2(cookieMatrix) * (projPos.xy-vec2(0.5)) + vec2(0.5);\n    return mix(vec4(1.0), texture2D(tex, uv), intensity);\n}\nvec4 getCookieCube(samplerCube tex, mat4 transform, float intensity) {\n    return mix(vec4(1.0), textureCube(tex, dLightDirNormW * mat3(transform)), intensity);\n}\n";
pc.shaderChunks.cubeMapProjectBoxPS = "uniform vec3 envBoxMin, envBoxMax;\nvec3 cubeMapProject(vec3 nrdir) {\n    vec3 rbmax = (envBoxMax - vPositionW) / nrdir;\n    vec3 rbmin = (envBoxMin - vPositionW) / nrdir;\n    vec3 rbminmax;\n    rbminmax.x = nrdir.x>0.0? rbmax.x : rbmin.x;\n    rbminmax.y = nrdir.y>0.0? rbmax.y : rbmin.y;\n    rbminmax.z = nrdir.z>0.0? rbmax.z : rbmin.z;\n    float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);\n    vec3 posonbox = vPositionW + nrdir * fa;\n    vec3 envBoxPos = (envBoxMin + envBoxMax) * 0.5;\n    return posonbox - envBoxPos;\n}\n";
pc.shaderChunks.cubeMapProjectNonePS = "vec3 cubeMapProject(vec3 dir) {\n    return dir;\n}\n";
pc.shaderChunks.diffuseConstPS = "uniform vec3 material_diffuse;\nvoid getAlbedo() {\n    dAlbedo = material_diffuse.rgb;\n}\n";
pc.shaderChunks.diffuseTexPS = "uniform sampler2D texture_diffuseMap;\nvoid getAlbedo() {\n    dAlbedo = texture2DSRGB(texture_diffuseMap, $UV).$CH;\n}\n";
pc.shaderChunks.diffuseTexConstPS = "uniform sampler2D texture_diffuseMap;\nuniform vec3 material_diffuse;\nvoid getAlbedo() {\n    dAlbedo = texture2DSRGB(texture_diffuseMap, $UV).$CH * material_diffuse;\n}\n";
pc.shaderChunks.diffuseVertPS = "void getAlbedo() {\n    dAlbedo = gammaCorrectInput(saturate(vVertexColor.$CH));\n}\n";
pc.shaderChunks.diffuseVertConstPS = "uniform vec3 material_diffuse;\nvoid getAlbedo() {\n    dAlbedo = gammaCorrectInput(saturate(vVertexColor.$CH)) * material_diffuse;\n}\n";
pc.shaderChunks.dilatePS = "varying vec2 vUv0;\nuniform sampler2D source;\nuniform vec2 pixelOffset;\nvoid main(void) {\n    vec4 c = texture2D(source, vUv0);\n    c = c.a>0.0? c : texture2D(source, vUv0 - pixelOffset);\n    c = c.a>0.0? c : texture2D(source, vUv0 + vec2(0, -pixelOffset.y));\n    c = c.a>0.0? c : texture2D(source, vUv0 + vec2(pixelOffset.x, -pixelOffset.y));\n    c = c.a>0.0? c : texture2D(source, vUv0 + vec2(-pixelOffset.x, 0));\n    c = c.a>0.0? c : texture2D(source, vUv0 + vec2(pixelOffset.x, 0));\n    c = c.a>0.0? c : texture2D(source, vUv0 + vec2(-pixelOffset.x, pixelOffset.y));\n    c = c.a>0.0? c : texture2D(source, vUv0 + vec2(0, pixelOffset.y));\n    c = c.a>0.0? c : texture2D(source, vUv0 + pixelOffset);\n    gl_FragColor = c;\n}\n";
pc.shaderChunks.dpAtlasQuadPS = "varying vec2 vUv0;\nuniform sampler2D source;\nuniform vec4 params;\nvoid main(void) {\n    vec2 uv = vUv0;\n    uv = uv * 2.0 - vec2(1.0);\n    uv *= params.xy;\n    uv = uv * 0.5 + 0.5;\n    gl_FragColor = texture2D(source, uv);\n}\n";
pc.shaderChunks.emissiveConstPS = "uniform vec3 material_emissive;\nvec3 getEmission() {\n    return material_emissive;\n}\n";
pc.shaderChunks.emissiveTexPS = "uniform sampler2D texture_emissiveMap;\nvec3 getEmission() {\n    return $texture2DSAMPLE(texture_emissiveMap, $UV).$CH;\n}\n";
pc.shaderChunks.emissiveTexConstPS = "uniform sampler2D texture_emissiveMap;\nuniform vec3 material_emissive;\nvec3 getEmission() {\n    return $texture2DSAMPLE(texture_emissiveMap, $UV).$CH * material_emissive;\n}\n";
pc.shaderChunks.emissiveTexConstFloatPS = "uniform sampler2D texture_emissiveMap;\nuniform float material_emissiveIntensity;\nvec3 getEmission() {\n    return $texture2DSAMPLE(texture_emissiveMap, $UV).$CH * material_emissiveIntensity;\n}\n";
pc.shaderChunks.emissiveVertPS = "vec3 getEmission() {\n    return gammaCorrectInput(saturate(vVertexColor.$CH));\n}\n";
pc.shaderChunks.emissiveVertConstPS = "uniform vec3 material_emissive;\nvec3 getEmission() {\n    return gammaCorrectInput(saturate(vVertexColor.$CH)) * material_emissive;\n}\n";
pc.shaderChunks.emissiveVertConstFloatPS = "uniform float material_emissiveIntensity;\nvec3 getEmission() {\n    return gammaCorrectInput(saturate(vVertexColor.$CH)) * material_emissiveIntensity;\n}\n";
pc.shaderChunks.endPS = "   gl_FragColor.rgb = combineColor();\n   gl_FragColor.rgb += getEmission();\n   gl_FragColor.rgb = addFog(gl_FragColor.rgb);\n   gl_FragColor.rgb = toneMap(gl_FragColor.rgb);\n   gl_FragColor.rgb = gammaCorrectOutput(gl_FragColor.rgb);\n";
pc.shaderChunks.envConstPS = "vec3 processEnvironment(vec3 color) {\n    return color;\n}\n";
pc.shaderChunks.envMultiplyPS = "uniform float skyboxIntensity;\nvec3 processEnvironment(vec3 color) {\n    return color * skyboxIntensity;\n}\n";
pc.shaderChunks.extensionPS = "";
pc.shaderChunks.extensionVS = "\n";
pc.shaderChunks.falloffInvSquaredPS = "float getFalloffInvSquared(float lightRadius) {\n    float sqrDist = dot(dLightDirW, dLightDirW);\n    float falloff = 1.0 / (sqrDist + 1.0);\n    float invRadius = 1.0 / lightRadius;\n    falloff *= 16.0;\n    falloff *= square( saturate( 1.0 - square( sqrDist * square(invRadius) ) ) );\n    return falloff;\n}\n";
pc.shaderChunks.falloffLinearPS = "float getFalloffLinear(float lightRadius) {\n    float d = length(dLightDirW);\n    return max(((lightRadius - d) / lightRadius), 0.0);\n}\n";
pc.shaderChunks.fixCubemapSeamsNonePS = "vec3 fixSeams(vec3 vec, float mipmapIndex) {\n    return vec;\n}\nvec3 fixSeams(vec3 vec) {\n    return vec;\n}\nvec3 fixSeamsStatic(vec3 vec, float invRecMipSize) {\n    return vec;\n}\n";
pc.shaderChunks.fixCubemapSeamsStretchPS = "vec3 fixSeams(vec3 vec, float mipmapIndex) {\n    float scale = 1.0 - exp2(mipmapIndex) / 128.0;\n    float M = max(max(abs(vec.x), abs(vec.y)), abs(vec.z));\n    if (abs(vec.x) != M) vec.x *= scale;\n    if (abs(vec.y) != M) vec.y *= scale;\n    if (abs(vec.z) != M) vec.z *= scale;\n    return vec;\n}\nvec3 fixSeams(vec3 vec) {\n    float scale = 1.0 - 1.0 / 128.0;\n    float M = max(max(abs(vec.x), abs(vec.y)), abs(vec.z));\n    if (abs(vec.x) != M) vec.x *= scale;\n    if (abs(vec.y) != M) vec.y *= scale;\n    if (abs(vec.z) != M) vec.z *= scale;\n    return vec;\n}\nvec3 fixSeamsStatic(vec3 vec, float invRecMipSize) {\n    float scale = invRecMipSize;\n    float M = max(max(abs(vec.x), abs(vec.y)), abs(vec.z));\n    if (abs(vec.x) != M) vec.x *= scale;\n    if (abs(vec.y) != M) vec.y *= scale;\n    if (abs(vec.z) != M) vec.z *= scale;\n    return vec;\n}\n";
pc.shaderChunks.fogExpPS = "uniform vec3 fog_color;\nuniform float fog_density;\nvec3 addFog(vec3 color) {\n    float depth = gl_FragCoord.z / gl_FragCoord.w;\n    float fogFactor = exp(-depth * fog_density);\n    fogFactor = clamp(fogFactor, 0.0, 1.0);\n    return mix(fog_color, color, fogFactor);\n}\n";
pc.shaderChunks.fogExp2PS = "uniform vec3 fog_color;\nuniform float fog_density;\nvec3 addFog(vec3 color) {\n    float depth = gl_FragCoord.z / gl_FragCoord.w;\n    float fogFactor = exp(-depth * depth * fog_density * fog_density);\n    fogFactor = clamp(fogFactor, 0.0, 1.0);\n    return mix(fog_color, color, fogFactor);\n}\n";
pc.shaderChunks.fogLinearPS = "uniform vec3 fog_color;\nuniform float fog_start;\nuniform float fog_end;\nvec3 addFog(vec3 color) {\n    float depth = gl_FragCoord.z / gl_FragCoord.w;\n    float fogFactor = (fog_end - depth) / (fog_end - fog_start);\n    fogFactor = clamp(fogFactor, 0.0, 1.0);\n    fogFactor = gammaCorrectInput(fogFactor);\n    return mix(fog_color, color, fogFactor);\n}\n";
pc.shaderChunks.fogNonePS = "vec3 addFog(vec3 color) {\n    return color;\n}\n";
pc.shaderChunks.fresnelSchlickPS = "// Schlick's approximation\nuniform float material_fresnelFactor; // unused\nvoid getFresnel() {\n    float fresnel = 1.0 - max(dot(dNormalW, dViewDirW), 0.0);\n    float fresnel2 = fresnel * fresnel;\n    fresnel *= fresnel2 * fresnel2;\n    fresnel *= dGlossiness * dGlossiness;\n    dSpecularity = dSpecularity + (1.0 - dSpecularity) * fresnel;\n}\n";
pc.shaderChunks.fullscreenQuadPS = "varying vec2 vUv0;\nuniform sampler2D source;\nvoid main(void) {\n    gl_FragColor = texture2D(source, vUv0);\n}\n";
pc.shaderChunks.fullscreenQuadVS = "attribute vec2 vertex_position;\nvarying vec2 vUv0;\nvoid main(void)\n{\n    gl_Position = vec4(vertex_position, 0.5, 1.0);\n    vUv0 = vertex_position.xy*0.5+0.5;\n}\n";
pc.shaderChunks.gamma1_0PS = "vec4 texture2DSRGB(sampler2D tex, vec2 uv) {\n    return texture2D(tex, uv);\n}\nvec4 textureCubeSRGB(samplerCube tex, vec3 uvw) {\n    return textureCube(tex, uvw);\n}\nvec3 gammaCorrectOutput(vec3 color) {\n    return color;\n}\nvec3 gammaCorrectInput(vec3 color) {\n    return color;\n}\nfloat gammaCorrectInput(float color) {\n    return color;\n}\nvec4 gammaCorrectInput(vec4 color) {\n    return color;\n}\n";
pc.shaderChunks.gamma2_2PS = "vec3 gammaCorrectInput(vec3 color) {\n    return pow(color, vec3(2.2));\n}\nfloat gammaCorrectInput(float color) {\n    return pow(color, 2.2);\n}\nvec4 gammaCorrectInput(vec4 color) {\n    return vec4(pow(color.rgb, vec3(2.2)), color.a);\n}\nvec4 texture2DSRGB(sampler2D tex, vec2 uv) {\n    vec4 rgba = texture2D(tex, uv);\n    rgba.rgb = gammaCorrectInput(rgba.rgb);\n    return rgba;\n}\nvec4 textureCubeSRGB(samplerCube tex, vec3 uvw) {\n    vec4 rgba = textureCube(tex, uvw);\n    rgba.rgb = gammaCorrectInput(rgba.rgb);\n    return rgba;\n}\nvec3 gammaCorrectOutput(vec3 color) {\n    color += vec3(0.0000001);\n    return pow(color, vec3(0.45));\n}\n";
pc.shaderChunks.gamma2_2FastPS = "vec3 gammaCorrectInput(vec3 color) {\n    return color * (color * (color * 0.305306011 + 0.682171111) + 0.012522878);\n}\nfloat gammaCorrectInput(float color) {\n    return color * (color * (color * 0.305306011 + 0.682171111) + 0.012522878);\n}\nvec4 gammaCorrectInput(vec4 color) {\n    return vec4(gammaCorrectInput(color.rgb), color.a);\n}\nvec4 texture2DSRGB(sampler2D tex, vec2 uv) {\n    vec4 rgba = texture2D(tex, uv);\n    rgba.rgb = gammaCorrectInput(rgba.rgb);\n    return rgba;\n}\nvec4 textureCubeSRGB(samplerCube tex, vec3 uvw) {\n    vec4 rgba = textureCube(tex, uvw);\n    rgba.rgb = gammaCorrectInput(rgba.rgb);\n    return rgba;\n}\nvec3 gammaCorrectOutput(vec3 color) {\n    color += vec3(0.0000001);\n    return pow(color, vec3(0.45));\n}\n";
pc.shaderChunks.genParaboloidPS = "varying vec2 vUv0;\nuniform samplerCube source;\nuniform vec4 params; // x = mip\nvoid main(void) {\n    vec2 uv = vUv0;\n    float side = uv.x < 0.5? 1.0 : -1.0;\n    vec2 tc;\n    tc.x = fract(uv.x * 2.0) * 2.0 - 1.0;\n    tc.y = uv.y * 2.0 - 1.0;\n    // scale projection a bit to have a little overlap for filtering\n    const float scale = 1.1;\n    tc *= scale;\n    vec3 dir;\n    dir.y = (dot(tc, tc) - 1.0) * side; // from 1.0 center to 0.0 borders quadratically\n    dir.xz = tc * -2.0;\n    dir.x *= -side * params.y; // flip original cubemap x instead of doing it at runtime\n    dir = fixSeams(dir, params.x);\n    vec4 color = textureCube(source, dir, -100.0);\n    gl_FragColor = color;\n}\n";
pc.shaderChunks.glossConstPS = "uniform float material_shininess;\nvoid getGlossiness() {\n    dGlossiness = material_shininess + 0.0000001;\n}\n";
pc.shaderChunks.glossTexPS = "uniform sampler2D texture_glossMap;\nvoid getGlossiness() {\n    dGlossiness = texture2D(texture_glossMap, $UV).$CH + 0.0000001;\n}\n";
pc.shaderChunks.glossTexConstPS = "uniform sampler2D texture_glossMap;\nuniform float material_shininess;\nvoid getGlossiness() {\n    dGlossiness = material_shininess * texture2D(texture_glossMap, $UV).$CH + 0.0000001;\n}\n";
pc.shaderChunks.glossVertPS = "void getGlossiness() {\n    dGlossiness = saturate(vVertexColor.$CH) + 0.0000001;\n}\n";
pc.shaderChunks.glossVertConstPS = "uniform float material_shininess;\nvoid getGlossiness() {\n    dGlossiness = material_shininess * saturate(vVertexColor.$CH) + 0.0000001;\n}\n";
pc.shaderChunks.instancingVS = "\nattribute vec4 instance_line1;\nattribute vec4 instance_line2;\nattribute vec4 instance_line3;\nattribute vec4 instance_line4;\n";
pc.shaderChunks.lightDiffuseLambertPS = "float getLightDiffuse() {\n    return max(dot(dNormalW, -dLightDirNormW), 0.0);\n}\n";
pc.shaderChunks.lightDirPointPS = "void getLightDirPoint(vec3 lightPosW) {\n    dLightDirW = vPositionW - lightPosW;\n    dLightDirNormW = normalize(dLightDirW);\n    dLightPosW = lightPosW;\n}\n";
pc.shaderChunks.lightSpecularBlinnPS = "// Energy-conserving (hopefully) Blinn-Phong\nfloat getLightSpecular() {\n    vec3 h = normalize( -dLightDirNormW + dViewDirW );\n    float nh = max( dot( h, dNormalW ), 0.0 );\n    float specPow = exp2(dGlossiness * 11.0); // glossiness is linear, power is not; 0 - 2048\n    specPow = antiAliasGlossiness(specPow);\n    // Hack: On Mac OS X, calling pow with zero for the exponent generates hideous artifacts so bias up a little\n    specPow = max(specPow, 0.0001);\n    return pow(nh, specPow) * (specPow + 2.0) / 8.0;\n}\n";
pc.shaderChunks.lightSpecularPhongPS = "float getLightSpecular() {\n    float specPow = dGlossiness;\n    specPow = antiAliasGlossiness(specPow);\n    // Hack: On Mac OS X, calling pow with zero for the exponent generates hideous artifacts so bias up a little\n    return pow(max(dot(dReflDirW, -dLightDirNormW), 0.0), specPow + 0.0001);\n}\n";
pc.shaderChunks.lightmapDirPS = "uniform sampler2D texture_lightMap;\nuniform sampler2D texture_dirLightMap;\nvoid addLightMap() {\n    vec3 color = $texture2DSAMPLE(texture_lightMap, $UV).$CH;\n    vec4 dir = texture2D(texture_dirLightMap, $UV);\n    if (dot(dir.xyz,vec3(1.0)) < 0.00001) {\n        dDiffuseLight += color;\n        return;\n    }\n    dLightDirNormW = normalize(dir.xyz * 2.0 - vec3(1.0));\n    float vlight = saturate(dot(dLightDirNormW, -vNormalW));\n    float flight = saturate(dot(dLightDirNormW, -dNormalW));\n    float nlight = (flight / max(vlight,0.01)) * 0.5;\n    dDiffuseLight += color * nlight * 2.0;\n}\nvoid addDirLightMap() {\n    vec4 dir = texture2D(texture_dirLightMap, $UV);\n    if (dot(dir.xyz,vec3(1.0)) < 0.00001) return;\n    vec3 color = $texture2DSAMPLE(texture_lightMap, $UV).$CH;\n    dLightDirNormW = normalize(dir.xyz * 2.0 - vec3(1.0));\n    dSpecularLight += vec3(getLightSpecular()) * color;\n}\n";
pc.shaderChunks.lightmapSinglePS = "uniform sampler2D texture_lightMap;\nvoid addLightMap() {\n    dDiffuseLight += $texture2DSAMPLE(texture_lightMap, $UV).$CH;\n}\n";
pc.shaderChunks.lightmapSingleVertPS = "void addLightMap() {\n    dDiffuseLight += saturate(vVertexColor.$CH);\n}\n";
pc.shaderChunks.metalnessPS = "void processMetalness(float metalness) {\n    const float dielectricF0 = 0.04;\n    dSpecularity = mix(vec3(dielectricF0), dAlbedo, metalness);\n    dAlbedo *= 1.0 - metalness;\n}\n";
pc.shaderChunks.metalnessConstPS = "uniform float material_metalness;\nvoid getSpecularity() {\n    processMetalness(material_metalness);\n}\n";
pc.shaderChunks.metalnessTexPS = "uniform sampler2D texture_metalnessMap;\nvoid getSpecularity() {\n    processMetalness(texture2D(texture_metalnessMap, $UV).$CH);\n}\n";
pc.shaderChunks.metalnessTexConstPS = "uniform sampler2D texture_metalnessMap;\nuniform float material_metalness;\nvoid getSpecularity() {\n    processMetalness(texture2D(texture_metalnessMap, $UV).$CH * material_metalness);\n}\n";
pc.shaderChunks.metalnessVertPS = "void getSpecularity() {\n    processMetalness(saturate(vVertexColor.$CH));\n}\n";
pc.shaderChunks.metalnessVertConstPS = "uniform float material_metalness;\nvoid getSpecularity() {\n    processMetalness(saturate(vVertexColor.$CH) * material_metalness);\n}\n";
pc.shaderChunks.msdfPS = "uniform sampler2D texture_msdfMap;\nfloat median(float r, float g, float b) {\n    return max(min(r, g), min(max(r, g), b));\n}\nvec4 applyMsdf(vec4 color) {\n    vec3 sample = texture2D(texture_msdfMap, vUv0).rgb;\n    float distance = median(sample.r, sample.g, sample.b) - 0.5;\n    vec4 msdf;\n    #ifdef GL_OES_standard_derivatives\n    vec4 background = vec4(0.0);\n    float opacity = clamp(distance/fwidth(distance) + 0.5, 0.0, 1.0);\n    msdf = mix(background, color, opacity);\n    if (msdf.a < 0.01) {\n        discard;\n    }\n    #else\n    msdf = color;\n    if (distance < 0.1) {\n        discard;\n    }\n    #endif\n    return msdf;\n}\n";
pc.shaderChunks.normalVS = "vec3 getNormal() {\n    dNormalMatrix = matrix_normal;\n    return normalize(dNormalMatrix * vertex_normal);\n}\n";
pc.shaderChunks.normalInstancedVS = "vec3 getNormal() {\n    dNormalMatrix = mat3(instance_line1.xyz, instance_line2.xyz, instance_line3.xyz);\n    return normalize(dNormalMatrix * vertex_normal);\n}\n";
pc.shaderChunks.normalMapPS = "uniform sampler2D texture_normalMap;\nuniform float material_bumpiness;\nvoid getNormal() {\n    vec3 normalMap = unpackNormal(texture2D(texture_normalMap, $UV));\n    dNormalMap = normalMap;\n    dNormalW = dTBN * normalMap;\n}\n";
pc.shaderChunks.normalMapFloatPS = "uniform sampler2D texture_normalMap;\nuniform float material_bumpiness;\nvoid getNormal() {\n    vec3 normalMap = unpackNormal(texture2D(texture_normalMap, $UV));\n    dNormalMap = normalMap;\n    normalMap = normalize(mix(vec3(0.0, 0.0, 1.0), normalMap, material_bumpiness));\n    dNormalW = dTBN * normalMap;\n}\n";
pc.shaderChunks.normalMapFloatTBNfastPS = "uniform sampler2D texture_normalMap;\nuniform float material_bumpiness;\nvoid getNormal() {\n    vec3 normalMap = unpackNormal(texture2D(texture_normalMap, $UV));\n    dNormalMap = normalMap;\n    normalMap = mix(vec3(0.0, 0.0, 1.0), normalMap, material_bumpiness);\n    dNormalW = normalize(dTBN * normalMap);\n}\n";
pc.shaderChunks.normalSkinnedVS = "vec3 getNormal() {\n    dNormalMatrix = mat3(dModelMatrix[0].xyz, dModelMatrix[1].xyz, dModelMatrix[2].xyz);\n    return normalize(dNormalMatrix * vertex_normal);\n}\n";
pc.shaderChunks.normalVertexPS = "void getNormal() {\n    dNormalW = normalize(vNormalW);\n}\n";
pc.shaderChunks.normalXYPS = "vec3 unpackNormal(vec4 nmap) {\n    vec3 normal;\n    normal.xy = nmap.wy * 2.0 - 1.0;\n    normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));\n    return normal;\n}\n";
pc.shaderChunks.normalXYZPS = "vec3 unpackNormal(vec4 nmap) {\n    return nmap.xyz * 2.0 - 1.0;\n}\n";
pc.shaderChunks.opacityConstPS = "uniform float material_opacity;\nvoid getOpacity() {\n    dAlpha = material_opacity;\n}\n";
pc.shaderChunks.opacityTexPS = "uniform sampler2D texture_opacityMap;\nvoid getOpacity() {\n    dAlpha = texture2D(texture_opacityMap, $UV).$CH;\n}\n";
pc.shaderChunks.opacityTexConstPS = "uniform sampler2D texture_opacityMap;\nuniform float material_opacity;\nvoid getOpacity() {\n    dAlpha = texture2D(texture_opacityMap, $UV).$CH * material_opacity;\n}\n";
pc.shaderChunks.opacityVertPS = "void getOpacity() {\n    dAlpha = saturate(vVertexColor.$CH);\n}\n";
pc.shaderChunks.opacityVertConstPS = "uniform float material_opacity;\nvoid getOpacity() {\n    dAlpha = saturate(vVertexColor.$CH) * material_opacity;\n}\n";
pc.shaderChunks.outputAlphaPS = "gl_FragColor.a = dAlpha;\n";
pc.shaderChunks.outputAlphaOpaquePS = "gl_FragColor.a = 1.0;\n";
pc.shaderChunks.outputAlphaPremulPS = "gl_FragColor.rgb *= dAlpha;\ngl_FragColor.a = dAlpha;\n";
pc.shaderChunks.outputCubemapPS = "varying vec2 vUv0;\nuniform samplerCube source;\nuniform vec4 params;\nfloat saturate(float x) {\n    return clamp(x, 0.0, 1.0);\n}\nvec4 encodeRGBM(vec4 color) { // modified RGBM\n    color.rgb = pow(color.rgb, vec3(0.5));\n    color.rgb *= 1.0 / 8.0;\n    color.a = saturate( max( max( color.r, color.g ), max( color.b, 1.0 / 255.0 ) ) );\n    color.a = ceil(color.a * 255.0) / 255.0;\n    color.rgb /= color.a;\n    return color;\n}\nvoid main(void) {\n    vec2 st = vUv0 * 2.0 - 1.0;\n    float face = params.x;\n    vec3 vec;\n    if (face==0.0) {\n        vec = vec3(1, -st.y, -st.x);\n    } else if (face==1.0) {\n        vec = vec3(-1, -st.y, st.x);\n    } else if (face==2.0) {\n        vec = vec3(st.x, 1, st.y);\n    } else if (face==3.0) {\n        vec = vec3(st.x, -1, -st.y);\n    } else if (face==4.0) {\n        vec = vec3(st.x, -st.y, 1);\n    } else {\n        vec = vec3(-st.x, -st.y, -1);\n    }\n    gl_FragColor = textureCube(source, vec);\n    if (params.w >= 2.0) gl_FragColor = encodeRGBM(gl_FragColor);\n}\n";
pc.shaderChunks.packDepthPS = "// Packing a float in GLSL with multiplication and mod\n// http://blog.gradientstudios.com/2012/08/23/shadow-map-improvement\nvec4 packFloat(float depth) {\n    const vec4 bit_shift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);\n    const vec4 bit_mask  = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\n    // combination of mod and multiplication and division works better\n    vec4 res = mod(depth * bit_shift * vec4(255), vec4(256) ) / vec4(255);\n    res -= res.xxyz * bit_mask;\n    return res;\n}\n";
pc.shaderChunks.packDepthMaskPS = "vec4 packFloat(float depth) {\n    const vec4 bit_shift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);\n    const vec4 bit_mask  = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\n    // combination of mod and multiplication and division works better\n    vec4 res = mod(depth * bit_shift * vec4(255), vec4(256) ) / vec4(255);\n    res.x = 0.0;\n    res -= res.xxyz * bit_mask;\n    return res;\n}\n";
pc.shaderChunks.parallaxPS = "uniform sampler2D texture_heightMap;\nuniform float material_heightMapFactor;\nvoid getParallax() {\n    float parallaxScale = material_heightMapFactor;\n    float height = texture2D(texture_heightMap, $UV).$CH;\n    height = height * parallaxScale - parallaxScale*0.5;\n    vec3 viewDirT = dViewDirW * dTBN;\n    viewDirT.z += 0.42;\n    dUvOffset = height * (viewDirT.xy / viewDirT.z);\n}\n";
pc.shaderChunks.particlePS = "varying vec4 texCoordsAlphaLife;\nuniform sampler2D colorMap;\nuniform sampler2D internalTex3;\nuniform float graphSampleSize;\nuniform float graphNumSamples;\nuniform float camera_far;\nuniform float softening;\nuniform float colorMult;\nfloat saturate(float x) {\n    return clamp(x, 0.0, 1.0);\n}\nfloat unpackFloat(vec4 rgbaDepth) {\n    const vec4 bitShift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);\n    float depth = dot(rgbaDepth, bitShift);\n    return depth;\n}\nvoid main(void) {\n    vec4 tex         = texture2DSRGB(colorMap, texCoordsAlphaLife.xy);\n    vec4 ramp     = texture2DSRGB(internalTex3, vec2(texCoordsAlphaLife.w, 0.0));\n    ramp.rgb *= colorMult;\n    ramp.a += texCoordsAlphaLife.z;\n    vec3 rgb =     tex.rgb * ramp.rgb;\n    float a =         tex.a * ramp.a;\n";
pc.shaderChunks.particleVS = "\nvec3 unpack3NFloats(float src) {\n    float r = fract(src);\n    float g = fract(src * 256.0);\n    float b = fract(src * 65536.0);\n    return vec3(r, g, b);\n}\nfloat saturate(float x) {\n    return clamp(x, 0.0, 1.0);\n}\nvec4 tex1Dlod_lerp(sampler2D tex, vec2 tc) {\n    return mix( texture2D(tex,tc), texture2D(tex,tc + graphSampleSize), fract(tc.x*graphNumSamples) );\n}\nvec4 tex1Dlod_lerp(sampler2D tex, vec2 tc, out vec3 w) {\n    vec4 a = texture2D(tex,tc);\n    vec4 b = texture2D(tex,tc + graphSampleSize);\n    float c = fract(tc.x*graphNumSamples);\n    vec3 unpackedA = unpack3NFloats(a.w);\n    vec3 unpackedB = unpack3NFloats(b.w);\n    w = mix(unpackedA, unpackedB, c);\n    return mix(a, b, c);\n}\nvec2 rotate(vec2 quadXY, float pRotation, out mat2 rotMatrix) {\n    float c = cos(pRotation);\n    float s = sin(pRotation);\n    mat2 m = mat2(c, -s, s, c);\n    rotMatrix = m;\n    return m * quadXY;\n}\nvec3 billboard(vec3 InstanceCoords, vec2 quadXY, out mat3 localMat) {\n    vec3 viewUp = matrix_viewInverse[1].xyz;\n    vec3 posCam = matrix_viewInverse[3].xyz;\n    mat3 billMat;\n    billMat[2] = normalize(InstanceCoords - posCam);\n    billMat[0] = normalize(cross(viewUp, billMat[2]));\n    billMat[1] = -viewUp;\n    vec3 pos = billMat * vec3(quadXY, 0);\n    localMat = billMat;\n    return pos;\n}\nvoid main(void) {\n    vec3 meshLocalPos = particle_vertexData.xyz;\n    float id = floor(particle_vertexData.w);\n    float rndFactor = fract(sin(id + 1.0 + seed));\n    vec3 rndFactor3 = vec3(rndFactor, fract(rndFactor*10.0), fract(rndFactor*100.0));\n    float uv = id / numParticlesPot;\n    readInput(uv);\n    vec2 velocityV = normalize((mat3(matrix_view) * inVel).xy); // should be removed by compiler if align/stretch is not used\n    float particleLifetime = lifetime;\n    if (inLife <= 0.0 || inLife > particleLifetime || !inShow) meshLocalPos = vec3(0.0);\n    vec2 quadXY = meshLocalPos.xy;\n    float nlife = clamp(inLife / particleLifetime, 0.0, 1.0);\n    vec3 paramDiv;\n    vec4 params = tex1Dlod_lerp(internalTex2, vec2(nlife, 0), paramDiv);\n    float scale = params.y;\n    float scaleDiv = paramDiv.x;\n    float alphaDiv = paramDiv.z;\n    scale += (scaleDiv * 2.0 - 1.0) * scaleDivMult * fract(rndFactor*10000.0);\n    texCoordsAlphaLife = vec4(quadXY * -0.5 + 0.5,    (alphaDiv * 2.0 - 1.0) * alphaDivMult * fract(rndFactor*1000.0),    nlife);\n    vec3 particlePos = inPos;\n    vec3 particlePosMoved = vec3(0.0);\n    mat2 rotMatrix;\n    mat3 localMat;\n";
pc.shaderChunks.particleAnimFrameClampVS = "\n    float animFrame = min(floor(texCoordsAlphaLife.w * animTexParams.z), animTexParams.w);\n";
pc.shaderChunks.particleAnimFrameLoopVS = "\n    float animFrame = floor(texCoordsAlphaLife.w * animTexParams.z);\n";
pc.shaderChunks.particleAnimTexVS = "\n    float atlasX = animFrame * animTexParams.x;\n    float atlasY = floor(atlasX) * animTexParams.y;\n    atlasX = fract(atlasX);\n    texCoordsAlphaLife.xy *= animTexParams.xy;\n    texCoordsAlphaLife.xy += vec2(atlasX, atlasY);\n    texCoordsAlphaLife.y = 1.0 - texCoordsAlphaLife.y;\n";
pc.shaderChunks.particleInputFloatPS = "void readInput(float uv) {\n    vec4 tex = texture2D(particleTexIN, vec2(uv, 0.25));\n    vec4 tex2 = texture2D(particleTexIN, vec2(uv, 0.75));\n    inPos = tex.xyz;\n    inVel = tex2.xyz;\n    inAngle = (tex.w < 0.0? -tex.w : tex.w) - 1000.0;\n    inShow = tex.w >= 0.0;\n    inLife = tex2.w;\n}\n";
pc.shaderChunks.particleInputRgba8PS = "//RG=X, BA=Y\n//RG=Z, BA=A\n//RGB=V, A=visMode\n//RGBA=life\n#define PI2 6.283185307179586\nuniform vec3 inBoundsSize;\nuniform vec3 inBoundsCenter;\nuniform float maxVel;\nfloat decodeFloatRG(vec2 rg) {\n    return rg.y*(1.0/255.0) + rg.x;\n}\nfloat decodeFloatRGBA( vec4 rgba ) {\n  return dot( rgba, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/160581375.0) );\n}\nvoid readInput(float uv) {\n    vec4 tex0 = texture2D(particleTexIN, vec2(uv, 0.125));\n    vec4 tex1 = texture2D(particleTexIN, vec2(uv, 0.375));\n    vec4 tex2 = texture2D(particleTexIN, vec2(uv, 0.625));\n    vec4 tex3 = texture2D(particleTexIN, vec2(uv, 0.875));\n    inPos = vec3(decodeFloatRG(tex0.rg), decodeFloatRG(tex0.ba), decodeFloatRG(tex1.rg));\n    inPos = (inPos - vec3(0.5)) * inBoundsSize + inBoundsCenter;\n    inVel = tex2.xyz;\n    inVel = (inVel - vec3(0.5)) * maxVel;\n    inAngle = decodeFloatRG(tex1.ba) * PI2;\n    inShow = tex2.a > 0.5;\n    inLife = decodeFloatRGBA(tex3);\n    float maxNegLife = max(lifetime, (numParticles - 1.0) * (rate+rateDiv));\n    float maxPosLife = lifetime+1.0;\n    inLife = inLife * (maxNegLife + maxPosLife) - maxNegLife;\n}\n";
pc.shaderChunks.particleOutputFloatPS = "void writeOutput() {\n    if (gl_FragCoord.y<1.0) {\n        gl_FragColor = vec4(outPos, (outAngle + 1000.0) * visMode);\n    } else {\n        gl_FragColor = vec4(outVel, outLife);\n    }\n}\n";
pc.shaderChunks.particleOutputRgba8PS = "uniform vec3 outBoundsMul;\nuniform vec3 outBoundsAdd;\nvec2 encodeFloatRG( float v ) {\n  vec2 enc = vec2(1.0, 255.0) * v;\n  enc = fract(enc);\n  enc -= enc.yy * vec2(1.0/255.0, 1.0/255.0);\n  return enc;\n}\nvec4 encodeFloatRGBA( float v ) {\n  vec4 enc = vec4(1.0, 255.0, 65025.0, 160581375.0) * v;\n  enc = fract(enc);\n  enc -= enc.yzww * vec4(1.0/255.0,1.0/255.0,1.0/255.0,0.0);\n  return enc;\n}\nvoid writeOutput() {\n    //outPos = (outPos - outBoundsCenter) / outBoundsSize + vec3(0.5);\n    outPos = outPos * outBoundsMul + outBoundsAdd;\n    outAngle = fract(outAngle / PI2);\n    outVel = (outVel / maxVel) + vec3(0.5); // TODO: mul\n    float maxNegLife = max(lifetime, (numParticles - 1.0) * (rate+rateDiv));\n    float maxPosLife = lifetime+1.0;\n    outLife = (outLife + maxNegLife) / (maxNegLife + maxPosLife);\n    if (gl_FragCoord.y < 1.0) {\n        gl_FragColor = vec4(encodeFloatRG(outPos.x), encodeFloatRG(outPos.y));\n    } else if (gl_FragCoord.y < 2.0) {\n        gl_FragColor = vec4(encodeFloatRG(outPos.z), encodeFloatRG(outAngle));\n    } else if (gl_FragCoord.y < 3.0) {\n        gl_FragColor = vec4(outVel, visMode*0.5+0.5);\n    } else {\n        gl_FragColor = encodeFloatRGBA(outLife);\n    }\n}\n";
pc.shaderChunks.particleUpdaterAABBPS = "uniform mat3 spawnBounds;\nvec3 calcSpawnPosition(vec3 inBounds, float rndFactor) {\n    return emitterPos + spawnBounds * (inBounds - vec3(0.5));\n}\nvoid addInitialVelocity(inout vec3 localVelocity, vec3 inBounds) {\n    localVelocity -= vec3(0, 0, initialVelocity);\n}\n";
pc.shaderChunks.particleUpdaterEndPS = "\n    writeOutput();\n}\n";
pc.shaderChunks.particleUpdaterInitPS = "varying vec2 vUv0;\nuniform sampler2D particleTexIN;\nuniform sampler2D internalTex0;\nuniform sampler2D internalTex1;\nuniform sampler2D internalTex2;\nuniform mat3 emitterMatrix;\nuniform vec3 emitterScale;\nuniform vec3 emitterPos, frameRandom, localVelocityDivMult, velocityDivMult;\nuniform float delta, rate, rateDiv, lifetime, numParticles, rotSpeedDivMult, seed;\nuniform float startAngle, startAngle2;\nuniform float initialVelocity;\nuniform float graphSampleSize;\nuniform float graphNumSamples;\nvec3 inPos;\nvec3 inVel;\nfloat inAngle;\nbool inShow;\nfloat inLife;\nfloat visMode;\nvec3 outPos;\nvec3 outVel;\nfloat outAngle;\nbool outShow;\nfloat outLife;\n";
pc.shaderChunks.particleUpdaterNoRespawnPS = "    if (outLife >= lifetime) {\n        outLife -= max(lifetime, (numParticles - 1.0) * particleRate);\n        visMode = -1.0;\n    }\n";
pc.shaderChunks.particleUpdaterOnStopPS = "    visMode = outLife < 0.0? -1.0: visMode;\n";
pc.shaderChunks.particleUpdaterRespawnPS = "    if (outLife >= lifetime) {\n        outLife -= max(lifetime, (numParticles - 1.0) * particleRate);\n        visMode = 1.0;\n    }\n    visMode = outLife < 0.0? 1.0: visMode;\n";
pc.shaderChunks.particleUpdaterSpherePS = "uniform float spawnBoundsSphere;\nvec3 calcSpawnPosition(vec3 inBounds, float rndFactor) {\n    float rnd4 = fract(rndFactor * 1000.0);\n    return emitterPos + normalize(inBounds.xyz - vec3(0.5)) * rnd4 * spawnBoundsSphere;\n}\nvoid addInitialVelocity(inout vec3 localVelocity, vec3 inBounds) {\n    localVelocity += normalize(inBounds - vec3(0.5)) * initialVelocity;\n}\n";
pc.shaderChunks.particleUpdaterStartPS = "float saturate(float x) {\n    return clamp(x, 0.0, 1.0);\n}\nvec3 unpack3NFloats(float src) {\n    float r = fract(src);\n    float g = fract(src * 256.0);\n    float b = fract(src * 65536.0);\n    return vec3(r, g, b);\n}\nvec3 tex1Dlod_lerp(sampler2D tex, vec2 tc, out vec3 w) {\n    vec4 a = texture2D(tex, tc);\n    vec4 b = texture2D(tex, tc + graphSampleSize);\n    float c = fract(tc.x * graphNumSamples);\n    vec3 unpackedA = unpack3NFloats(a.w);\n    vec3 unpackedB = unpack3NFloats(b.w);\n    w = mix(unpackedA, unpackedB, c);\n    return mix(a.xyz, b.xyz, c);\n}\n#define HASHSCALE4 vec4(1031, .1030, .0973, .1099)\nvec4 hash41(float p) {\n    vec4 p4 = fract(vec4(p) * HASHSCALE4);\n    p4 += dot(p4, p4.wzxy+19.19);\n    return fract(vec4((p4.x + p4.y)*p4.z, (p4.x + p4.z)*p4.y, (p4.y + p4.z)*p4.w, (p4.z + p4.w)*p4.x));\n}\nvoid main(void)\n{\n    if (gl_FragCoord.x > numParticles) discard;\n    readInput(vUv0.x);\n    visMode = inShow? 1.0 : -1.0;\n    vec4 rndFactor = hash41(gl_FragCoord.x + seed);\n    float particleRate = rate + rateDiv * rndFactor.x;\n    outLife = inLife + delta;\n    float nlife = clamp(outLife / lifetime, 0.0, 1.0);\n    vec3 localVelocityDiv;\n    vec3 velocityDiv;\n    vec3 paramDiv;\n    vec3 localVelocity = tex1Dlod_lerp(internalTex0, vec2(nlife, 0), localVelocityDiv);\n    vec3 velocity =      tex1Dlod_lerp(internalTex1, vec2(nlife, 0), velocityDiv);\n    vec3 params =        tex1Dlod_lerp(internalTex2, vec2(nlife, 0), paramDiv);\n    float rotSpeed = params.x;\n    float rotSpeedDiv = paramDiv.y;\n    localVelocity +=    (localVelocityDiv * vec3(2.0) - vec3(1.0)) * localVelocityDivMult * rndFactor.xyz;\n    velocity +=         (velocityDiv * vec3(2.0) - vec3(1.0)) * velocityDivMult * rndFactor.xyz;\n    rotSpeed +=         (rotSpeedDiv * 2.0 - 1.0) * rotSpeedDivMult * rndFactor.y;\n    addInitialVelocity(localVelocity, rndFactor.xyz);\n    outVel = emitterMatrix * localVelocity.xyz + velocity.xyz * emitterScale;\n    outPos = inPos + outVel * delta;\n    outAngle = inAngle + rotSpeed * delta;\n    bool respawn = outLife <= 0.0 || outLife >= lifetime;\n    outPos = respawn? calcSpawnPosition(rndFactor.xyz, rndFactor.x) : outPos;\n    outAngle = respawn? mix(startAngle, startAngle2, rndFactor.x) : outAngle;\n    outVel = respawn? vec3(0.0) : outVel;\n";
pc.shaderChunks.particle_TBNVS = "\n    mat3 rot3 = mat3(rotMatrix[0][0], rotMatrix[0][1], 0.0,        rotMatrix[1][0], rotMatrix[1][1], 0.0,        0.0, 0.0, 1.0);\n    localMat[2] *= -1.0;\n    ParticleMat = localMat * rot3;\n";
pc.shaderChunks.particle_billboardVS = "\n    quadXY = rotate(quadXY, inAngle, rotMatrix);\n    vec3 localPos = billboard(particlePos, quadXY, localMat);\n";
pc.shaderChunks.particle_blendAddPS = "\n    rgb *= saturate(gammaCorrectInput(a));\n    if ((rgb.r + rgb.g + rgb.b) < 0.000001) discard;\n";
pc.shaderChunks.particle_blendMultiplyPS = "\n    rgb = mix(vec3(1.0), rgb, vec3(a));\n    if (rgb.r + rgb.g + rgb.b > 2.99) discard;\n";
pc.shaderChunks.particle_blendNormalPS = "\n    if (a < 0.01) discard;\n";
pc.shaderChunks.particle_cpuVS = "attribute vec4 particle_vertexData;     // XYZ = world pos, W = life\nattribute vec4 particle_vertexData2;     // X = angle, Y = scale, Z = alpha, W = velocity.x\nattribute vec4 particle_vertexData3;     // XYZ = particle local pos, W = velocity.y\nattribute vec2 particle_vertexData4;     // X = velocity.z, W = particle ID\nuniform mat4 matrix_viewProjection;\nuniform mat4 matrix_model;\nuniform mat4 matrix_view;\nuniform mat3 matrix_normal;\nuniform mat4 matrix_viewInverse;\nuniform float numParticles;\nuniform float lifetime;\nuniform float stretch;\n//uniform float graphSampleSize;\n//uniform float graphNumSamples;\nuniform vec3 wrapBounds, emitterScale;\nuniform sampler2D texLifeAndSourcePosOUT;\nuniform sampler2D internalTex0;\nuniform sampler2D internalTex1;\nuniform sampler2D internalTex2;\nuniform vec3 emitterPos;\nvarying vec4 texCoordsAlphaLife;\nvec2 rotate(vec2 quadXY, float pRotation, out mat2 rotMatrix)\n{\n    float c = cos(pRotation);\n    float s = sin(pRotation);\n    //vec4 rotationMatrix = vec4(c, -s, s, c);\n    mat2 m = mat2(c, -s, s, c);\n    rotMatrix = m;\n    return m * quadXY;\n}\nvec3 billboard(vec3 InstanceCoords, vec2 quadXY, out mat3 localMat)\n{\n    vec3 viewUp = matrix_viewInverse[1].xyz;\n    vec3 posCam = matrix_viewInverse[3].xyz;\n    mat3 billMat;\n    billMat[2] = normalize(InstanceCoords - posCam);\n    billMat[0] = normalize(cross(viewUp, billMat[2]));\n    billMat[1] = -viewUp;\n    vec3 pos = billMat * vec3(quadXY, 0);\n    localMat = billMat;\n    return pos;\n}\nvoid main(void)\n{\n    vec3 particlePos = particle_vertexData.xyz;\n    vec3 inPos = particlePos;\n    vec3 vertPos = particle_vertexData3.xyz;\n    vec3 inVel = vec3(particle_vertexData2.w, particle_vertexData3.w, particle_vertexData4.x);\n    vec2 velocityV = normalize((mat3(matrix_view) * inVel).xy); // should be removed by compiler if align/stretch is not used\n    vec2 quadXY = vertPos.xy;\n    texCoordsAlphaLife = vec4(quadXY * -0.5 + 0.5, particle_vertexData2.z, particle_vertexData.w);\n    mat2 rotMatrix;\n    mat3 localMat;\n    float inAngle = particle_vertexData2.x;\n    vec3 particlePosMoved = vec3(0.0);\n    vec3 meshLocalPos = particle_vertexData3.xyz;\n";
pc.shaderChunks.particle_cpu_endVS = "\n    localPos *= particle_vertexData2.y * emitterScale;\n    localPos += particlePos;\n    gl_Position = matrix_viewProjection * vec4(localPos, 1.0);\n";
pc.shaderChunks.particle_endPS = "    rgb = addFog(rgb);\n    rgb = toneMap(rgb);\n    rgb = gammaCorrectOutput(rgb);\n    gl_FragColor = vec4(rgb, a);\n}\n";
pc.shaderChunks.particle_endVS = "\n    localPos *= scale * emitterScale;\n    localPos += particlePos;\n    gl_Position = matrix_viewProjection * vec4(localPos.xyz, 1.0);\n";
pc.shaderChunks.particle_halflambertPS = "\n    vec3 negNormal = normal*0.5+0.5;\n    vec3 posNormal = -normal*0.5+0.5;\n    negNormal *= negNormal;\n    posNormal *= posNormal;\n";
pc.shaderChunks.particle_initVS = "attribute vec4 particle_vertexData; // XYZ = particle position, W = particle ID + random factor\nuniform mat4 matrix_viewProjection;\nuniform mat4 matrix_model;\nuniform mat3 matrix_normal;\nuniform mat4 matrix_viewInverse;\nuniform mat4 matrix_view;\nuniform float numParticles, numParticlesPot;\nuniform float graphSampleSize;\nuniform float graphNumSamples;\nuniform float stretch;\nuniform vec3 wrapBounds;\nuniform vec3 emitterScale, emitterPos;\nuniform float rate, rateDiv, lifetime, deltaRandomnessStatic, scaleDivMult, alphaDivMult, seed, delta;\nuniform sampler2D particleTexOUT, particleTexIN;\nuniform sampler2D internalTex0;\nuniform sampler2D internalTex1;\nuniform sampler2D internalTex2;\nvarying vec4 texCoordsAlphaLife;\nvec3 inPos;\nvec3 inVel;\nfloat inAngle;\nbool inShow;\nfloat inLife;\n";
pc.shaderChunks.particle_lambertPS = "\n    vec3 negNormal = max(normal, vec3(0.0));\n    vec3 posNormal = max(-normal, vec3(0.0));\n";
pc.shaderChunks.particle_lightingPS = "\n    vec3 light = negNormal.x*lightCube[0] + posNormal.x*lightCube[1] +\n                        negNormal.y*lightCube[2] + posNormal.y*lightCube[3] +\n                        negNormal.z*lightCube[4] + posNormal.z*lightCube[5];\n    rgb *= light;\n";
pc.shaderChunks.particle_localShiftVS = "    particlePos += emitterPos;\n";
pc.shaderChunks.particle_meshVS = "\n    vec3 localPos = meshLocalPos;\n    localPos.xy = rotate(localPos.xy, inAngle, rotMatrix);\n    localPos.yz = rotate(localPos.yz, inAngle, rotMatrix);\n    billboard(particlePos, quadXY, localMat);\n";
pc.shaderChunks.particle_normalVS = "\n    Normal = normalize(localPos - localMat[2]);\n";
pc.shaderChunks.particle_normalMapPS = "\n    vec3 normalMap         = normalize( texture2D(normalMap, texCoordsAlphaLife.xy).xyz * 2.0 - 1.0 );\n    vec3 normal = ParticleMat * normalMap;\n";
pc.shaderChunks.particle_pointAlongVS = "    inAngle = atan(velocityV.x, velocityV.y); // not the fastest way, but easier to plug in; TODO: create rot matrix right from vectors\n";
pc.shaderChunks.particle_softPS = "\n    vec2 screenTC = gl_FragCoord.xy * uScreenSize.zw;\n    float depth = unpackFloat( texture2D(uDepthMap, screenTC) ) * camera_far;\n    float particleDepth = vDepth;\n    float depthDiff = saturate(abs(particleDepth - depth) * softening);\n    a *= depthDiff;\n";
pc.shaderChunks.particle_softVS = "\n    vDepth = -(matrix_view * vec4(localPos,1.0)).z;\n";
pc.shaderChunks.particle_stretchVS = "    vec3 moveDir = inVel * stretch;\n    vec3 posPrev = inPos - moveDir;\n    posPrev += particlePosMoved;\n    vec2 centerToVertexV = normalize((mat3(matrix_view) * localPos).xy);\n    float interpolation = dot(-velocityV, centerToVertexV) * 0.5 + 0.5;\n    particlePos = mix(particlePos, posPrev, interpolation);\n";
pc.shaderChunks.particle_wrapVS = "\n    vec3 origParticlePos = particlePos;\n    particlePos -= matrix_model[3].xyz;\n    particlePos = mod(particlePos, wrapBounds) - wrapBounds * 0.5;\n    particlePos += matrix_model[3].xyz;\n    particlePosMoved = particlePos - origParticlePos;\n";
pc.shaderChunks.precisionTestPS = "void main(void) {\n    gl_FragColor = vec4(2147483648.0);\n}\n";
pc.shaderChunks.precisionTest2PS = "uniform sampler2D source;\nvec4 packFloat(float depth) {\n    const vec4 bit_shift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);\n    const vec4 bit_mask  = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\n    vec4 res = mod(depth * bit_shift * vec4(255), vec4(256) ) / vec4(255);\n    res -= res.xxyz * bit_mask;\n    return res;\n}\nvoid main(void) {\n    float c = texture2D(source, vec2(0.0)).r;\n    float diff = abs(c - 2147483648.0) / 2147483648.0;\n    gl_FragColor = packFloat(diff);\n}\n";
pc.shaderChunks.prefilterCubemapPS = "varying vec2 vUv0;\nuniform samplerCube source;\nuniform vec4 params;\nfloat saturate(float x) {\n    return clamp(x, 0.0, 1.0);\n}\nfloat rnd(vec2 uv) {\n    return fract(sin(dot(uv, vec2(12.9898, 78.233) * 2.0)) * 43758.5453);\n}\nconst float PI = 3.14159265358979;\nvec3 hemisphereSample_cos(vec2 uv, mat3 vecSpace, vec3 cubeDir, float gloss) { // cos + lerped cone size (better than just lerped)\n    float phi = uv.y * 2.0 * PI;\n    float cosTheta = sqrt(1.0 - uv.x);\n    float sinTheta = sqrt(1.0 - cosTheta * cosTheta);\n    vec3 sampleDir = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);\n    return normalize(mix(vecSpace * sampleDir, cubeDir, params.y));\n}\nvec3 hemisphereSample_phong(vec2 uv, mat3 vecSpace, vec3 cubeDir, float specPow) {\n    float phi = uv.y * 2.0 * PI;\n    float cosTheta = pow(1.0 - uv.x, 1.0 / (specPow + 1.0));\n    float sinTheta = sqrt(1.0 - cosTheta * cosTheta);\n    vec3 sampleDir = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);\n    return vecSpace * sampleDir;\n}\nmat3 matrixFromVector(vec3 n) { // frisvad\n    float a = 1.0 / (1.0 + n.z);\n    float b = -n.x * n.y * a;\n    vec3 b1 = vec3(1.0 - n.x * n.x * a, b, -n.x);\n    vec3 b2 = vec3(b, 1.0 - n.y * n.y * a, -n.y);\n    return mat3(b1, b2, n);\n}\nvec4 encodeRGBM(vec3 color) { // modified RGBM\n    vec4 encoded;\n    encoded.rgb = pow(color.rgb, vec3(0.5));\n    encoded.rgb *= 1.0 / 8.0;\n    encoded.a = saturate( max( max( encoded.r, encoded.g ), max( encoded.b, 1.0 / 255.0 ) ) );\n    encoded.a = ceil(encoded.a * 255.0) / 255.0;\n    encoded.rgb /= encoded.a;\n    return encoded;\n}\nvoid main(void) {\n    vec2 st = vUv0 * 2.0 - 1.0;\n    if (params.w==1.0 || params.w==3.0) {\n        st = 2.0 * floor(gl_FragCoord.xy) / (params.z - 1.0) - 1.0;\n    }\n    float face = params.x;\n    vec3 vec;\n    if (face==0.0) {\n        vec = vec3(1, -st.y, -st.x);\n    } else if (face==1.0) {\n        vec = vec3(-1, -st.y, st.x);\n    } else if (face==2.0) {\n        vec = vec3(st.x, 1, st.y);\n    } else if (face==3.0) {\n        vec = vec3(st.x, -1, -st.y);\n    } else if (face==4.0) {\n        vec = vec3(st.x, -st.y, 1);\n    } else {\n        vec = vec3(-st.x, -st.y, -1);\n    }\n    mat3 vecSpace = matrixFromVector(normalize(vec));\n    vec3 color = vec3(0.0);\n    const int samples = $NUMSAMPLES;\n    vec3 vect;\n    for(int i=0; i<samples; i++) {\n        float sini = sin(float(i));\n        float cosi = cos(float(i));\n        float rand = rnd(vec2(sini, cosi));\n        vect = hemisphereSample_$METHOD(vec2(float(i) / float(samples), rand), vecSpace, vec, params.y);\n        color += $textureCube(source, vect).rgb;\n    }\n    color /= float(samples);\n    gl_FragColor = params.w < 2.0? vec4(color, 1.0) : encodeRGBM(color);\n}\n";
pc.shaderChunks.reflDirPS = "void getReflDir() {\n    dReflDirW = normalize(-reflect(dViewDirW, dNormalW));\n}\n";
pc.shaderChunks.reflectionCubePS = "uniform samplerCube texture_cubeMap;\nuniform float material_reflectivity;\nvoid addReflection() {\n    vec3 lookupVec = fixSeams(cubeMapProject(dReflDirW));\n    lookupVec.x *= -1.0;\n    dReflection += vec4($textureCubeSAMPLE(texture_cubeMap, lookupVec).rgb, material_reflectivity);\n}\n";
pc.shaderChunks.reflectionDpAtlasPS = "uniform sampler2D texture_sphereMap;\nuniform float material_reflectivity;\nvec2 getDpAtlasUv(vec2 uv, float mip) {\n    vec4 rect;\n    float sx = saturate(mip - 2.0);\n    rect.x = sx * 0.5;\n    float t = mip - rect.x * 6.0;\n    float i = 1.0 - rect.x;\n    rect.y = min(t * 0.5, 0.75) * i + rect.x;\n    float st = saturate(t);\n    rect.z = (1.0 - st * 0.5) * i;\n    rect.w = rect.z * 0.5;\n    float rcRectZ = 1.0 / rect.z;\n    float scaleFactor = 0.00390625 * rcRectZ; // 0.0078125 = (256 + 2) / 256 - 1, 0.00390625 same for 512\n    vec2 scale = vec2(scaleFactor, scaleFactor * 2.0);\n    uv = uv * (vec2(1.0) - scale) + scale * 0.5;\n    uv = uv * rect.zw + rect.xy;\n    return uv;\n}\nvoid addReflection() {\n    vec3 reflDir = normalize(cubeMapProject(dReflDirW));\n    // Convert vector to DP coords\n    bool up = reflDir.y > 0.0;\n    float scale = 0.90909090909090909090909090909091;// 1.0 / 1.1;\n    vec3 reflDirWarp = reflDir.xzx * vec3(-0.25, 0.5, 0.25);\n    float reflDirVer = abs(reflDir.y) + 1.0;\n    reflDirWarp /= reflDirVer;\n    reflDirWarp *= scale;\n    reflDirWarp = vec3(0.75, 0.5, 0.25) - reflDirWarp;\n    vec2 tc = up? reflDirWarp.xy : reflDirWarp.zy;\n    float bias = saturate(1.0 - dGlossiness) * 5.0; // multiply by max mip level\n    float mip = floor(bias);\n    vec3 tex1 = $texture2DSAMPLE(texture_sphereMap, getDpAtlasUv(tc, mip)).rgb;\n    mip = min(mip + 1.0, 5.0);\n    vec3 tex2 = $texture2DSAMPLE(texture_sphereMap, getDpAtlasUv(tc, mip)).rgb;\n    tex1 = mix(tex1, tex2, fract(bias));\n    tex1 = processEnvironment(tex1);\n    dReflection += vec4(tex1, material_reflectivity);\n}\n";
pc.shaderChunks.reflectionPrefilteredCubePS = "uniform samplerCube texture_prefilteredCubeMap128;\nuniform samplerCube texture_prefilteredCubeMap64;\nuniform samplerCube texture_prefilteredCubeMap32;\nuniform samplerCube texture_prefilteredCubeMap16;\nuniform samplerCube texture_prefilteredCubeMap8;\nuniform samplerCube texture_prefilteredCubeMap4;\nuniform float material_reflectivity;\nvoid addReflection() {\n    // Unfortunately, WebGL doesn't allow us using textureCubeLod. Therefore bunch of nasty workarounds is required.\n    // We fix mip0 to 128x128, so code is rather static.\n    // Mips smaller than 4x4 aren't great even for diffuse. Don't forget that we don't have bilinear filtering between different faces.\n    float bias = saturate(1.0 - dGlossiness) * 5.0; // multiply by max mip level\n    int index1 = int(bias);\n    int index2 = int(min(bias + 1.0, 7.0));\n    vec3 fixedReflDir = fixSeams(cubeMapProject(dReflDirW), bias);\n    fixedReflDir.x *= -1.0;\n    vec4 cubes[6];\n    cubes[0] = textureCube(texture_prefilteredCubeMap128, fixedReflDir);\n    cubes[1] = textureCube(texture_prefilteredCubeMap64, fixedReflDir);\n    cubes[2] = textureCube(texture_prefilteredCubeMap32, fixedReflDir);\n    cubes[3] = textureCube(texture_prefilteredCubeMap16, fixedReflDir);\n    cubes[4] = textureCube(texture_prefilteredCubeMap8, fixedReflDir);\n    cubes[5] = textureCube(texture_prefilteredCubeMap4, fixedReflDir);\n    // Also we don't have dynamic indexing in PS, so...\n    vec4 cube[2];\n    for(int i = 0; i < 6; i++) {\n        if (i == index1) {\n            cube[0] = cubes[i];\n        }\n        if (i == index2) {\n            cube[1] = cubes[i];\n        }\n    }\n    // another variant\n    /*if (index1==0){ cube[0]=cubes[0];\n    }else if (index1==1){ cube[0]=cubes[1];\n    }else if (index1==2){ cube[0]=cubes[2];\n    }else if (index1==3){ cube[0]=cubes[3];\n    }else if (index1==4){ cube[0]=cubes[4];\n    }else if (index1==5){ cube[0]=cubes[5];}\n    if (index2==0){ cube[1]=cubes[0];\n    }else if (index2==1){ cube[1]=cubes[1];\n    }else if (index2==2){ cube[1]=cubes[2];\n    }else if (index2==3){ cube[1]=cubes[3];\n    }else if (index2==4){ cube[1]=cubes[4];\n    }else if (index2==5){ cube[1]=cubes[5];}*/\n    vec4 cubeFinal = mix(cube[0], cube[1], fract(bias));\n    vec3 refl = processEnvironment($DECODE(cubeFinal).rgb);\n    dReflection += vec4(refl, material_reflectivity);\n}\n";
pc.shaderChunks.reflectionPrefilteredCubeLodPS = "#extension GL_EXT_shader_texture_lod : enable\nuniform samplerCube texture_prefilteredCubeMap128;\nuniform float material_reflectivity;\nvoid addReflection() {\n    float bias = saturate(1.0 - dGlossiness) * 5.0; // multiply by max mip level\n    vec3 fixedReflDir = fixSeams(cubeMapProject(dReflDirW), bias);\n    fixedReflDir.x *= -1.0;\n    vec3 refl = processEnvironment($DECODE( textureCubeLodEXT(texture_prefilteredCubeMap128, fixedReflDir, bias) ).rgb);\n    dReflection += vec4(refl, material_reflectivity);\n}\n";
pc.shaderChunks.reflectionSpherePS = "uniform mat4 matrix_view;\nuniform sampler2D texture_sphereMap;\nuniform float material_reflectivity;\nvoid addReflection() {\n    vec3 reflDirV = (mat3(matrix_view) * dReflDirW).xyz;\n    float m = 2.0 * sqrt( dot(reflDirV.xy, reflDirV.xy) + (reflDirV.z+1.0)*(reflDirV.z+1.0) );\n    vec2 sphereMapUv = reflDirV.xy / m + 0.5;\n    dReflection += vec4($texture2DSAMPLE(texture_sphereMap, sphereMapUv).rgb, material_reflectivity);\n}\n";
pc.shaderChunks.reflectionSphereLowPS = "uniform sampler2D texture_sphereMap;\nuniform float material_reflectivity;\nvoid addReflection() {\n    vec3 reflDirV = vNormalV;\n    vec2 sphereMapUv = reflDirV.xy * 0.5 + 0.5;\n    dReflection += vec4($texture2DSAMPLE(texture_sphereMap, sphereMapUv).rgb, material_reflectivity);\n}\n";
pc.shaderChunks.refractionPS = "uniform float material_refraction, material_refractionIndex;\nvec3 refract2(vec3 viewVec, vec3 Normal, float IOR) {\n    float vn = dot(viewVec, Normal);\n    float k = 1.0 - IOR * IOR * (1.0 - vn * vn);\n    vec3 refrVec = IOR * viewVec - (IOR * vn + sqrt(k)) * Normal;\n    return refrVec;\n}\nvoid addRefraction() {\n    // use same reflection code with refraction vector\n    vec3 tmp = dReflDirW;\n    vec4 tmp2 = dReflection;\n    dReflection = vec4(0.0);\n    dReflDirW = refract2(-dViewDirW, dNormalW, material_refractionIndex);\n    addReflection();\n    dDiffuseLight = mix(dDiffuseLight, dReflection.rgb * dAlbedo, material_refraction);\n    dReflDirW = tmp;\n    dReflection = tmp2;\n}\n";
pc.shaderChunks.rgbmPS = "vec3 decodeRGBM(vec4 rgbm) {\n    vec3 color = (8.0 * rgbm.a) * rgbm.rgb;\n    return color * color;\n}\nvec3 texture2DRGBM(sampler2D tex, vec2 uv) {\n    return decodeRGBM(texture2D(tex, uv));\n}\nvec3 textureCubeRGBM(samplerCube tex, vec3 uvw) {\n    return decodeRGBM(textureCube(tex, uvw));\n}\n";
pc.shaderChunks.shadowCommonPS = "void normalOffsetPointShadow(vec4 shadowParams) {\n    float distScale = length(dLightDirW);\n    vec3 wPos = vPositionW + vNormalW * shadowParams.y * clamp(1.0 - dot(vNormalW, -dLightDirNormW), 0.0, 1.0) * distScale; //0.02\n    vec3 dir = wPos - dLightPosW;\n    dLightDirW = dir;\n}\n";
pc.shaderChunks.shadowCoordPS = "void _getShadowCoordOrtho(mat4 shadowMatrix, vec3 shadowParams, vec3 wPos) {\n    dShadowCoord = (shadowMatrix * vec4(wPos, 1.0)).xyz;\n    dShadowCoord.z += getShadowBias(shadowParams.x, shadowParams.z);\n    dShadowCoord.z = min(dShadowCoord.z, 1.0);\n}\nvoid _getShadowCoordPersp(mat4 shadowMatrix, vec4 shadowParams, vec3 wPos) {\n    vec4 projPos = shadowMatrix * vec4(wPos, 1.0);\n    projPos.xy /= projPos.w;\n    dShadowCoord.xy = projPos.xy;\n    dShadowCoord.z = length(dLightDirW) * shadowParams.w;\n    dShadowCoord.z += getShadowBias(shadowParams.x, shadowParams.z);\n}\nvoid getShadowCoordOrtho(mat4 shadowMatrix, vec3 shadowParams) {\n    _getShadowCoordOrtho(shadowMatrix, shadowParams, vPositionW);\n}\nvoid getShadowCoordPersp(mat4 shadowMatrix, vec4 shadowParams) {\n    _getShadowCoordPersp(shadowMatrix, shadowParams, vPositionW);\n}\nvoid getShadowCoordPerspNormalOffset(mat4 shadowMatrix, vec4 shadowParams) {\n    float distScale = abs(dot(vPositionW - dLightPosW, dLightDirNormW)); // fov?\n    vec3 wPos = vPositionW + vNormalW * shadowParams.y * clamp(1.0 - dot(vNormalW, -dLightDirNormW), 0.0, 1.0) * distScale;\n    _getShadowCoordPersp(shadowMatrix, shadowParams, wPos);\n}\nvoid getShadowCoordOrthoNormalOffset(mat4 shadowMatrix, vec3 shadowParams) {\n    vec3 wPos = vPositionW + vNormalW * shadowParams.y * clamp(1.0 - dot(vNormalW, -dLightDirNormW), 0.0, 1.0); //0.08\n    _getShadowCoordOrtho(shadowMatrix, shadowParams, wPos);\n}\n";
pc.shaderChunks.shadowCoordVS = "void getLightDirPoint(vec3 lightPosW) {\n    vec3 lightDirW = vPositionW - lightPosW;\n    dLightDirNormW = normalize(lightDirW);\n    dLightPosW = lightPosW;\n}\nvoid _getShadowCoordOrtho(mat4 shadowMatrix, vec3 shadowParams, vec3 wPos) {\n    vec4 projPos = shadowMatrix * vec4(wPos, 1.0);\n    vMainShadowUv = projPos;\n}\nvoid _getShadowCoordPersp(mat4 shadowMatrix, vec3 shadowParams, vec3 wPos) {\n    vec4 projPos = shadowMatrix * vec4(wPos, 1.0);\n    vMainShadowUv = projPos;\n}\nvoid getShadowCoordOrtho(mat4 shadowMatrix, vec3 shadowParams) {\n    _getShadowCoordOrtho(shadowMatrix, shadowParams, vPositionW);\n}\nvoid getShadowCoordPersp(mat4 shadowMatrix, vec3 shadowParams) {\n    _getShadowCoordPersp(shadowMatrix, shadowParams, vPositionW);\n}\nvoid getShadowCoordPerspNormalOffset(mat4 shadowMatrix, vec3 shadowParams) {\n    float distScale = abs(dot(vPositionW - dLightPosW, dLightDirNormW)); // fov?\n    vec3 wPos = vPositionW + dNormalW * shadowParams.y * clamp(1.0 - dot(dNormalW, -dLightDirNormW), 0.0, 1.0) * distScale;\n    _getShadowCoordPersp(shadowMatrix, shadowParams, wPos);\n}\nvoid getShadowCoordOrthoNormalOffset(mat4 shadowMatrix, vec3 shadowParams) {\n    vec3 wPos = vPositionW + dNormalW * shadowParams.y * clamp(1.0 - dot(dNormalW, -dLightDirNormW), 0.0, 1.0); //0.08\n    _getShadowCoordOrtho(shadowMatrix, shadowParams, wPos);\n}\n";
pc.shaderChunks.shadowEVSMPS = "float VSM$(sampler2D tex, vec2 texCoords, float resolution, float Z, float vsmBias, float exponent) {\n    vec3 moments = texture2D(tex, texCoords).xyz;\n    return calculateEVSM(moments, Z, vsmBias, exponent);\n}\nfloat getShadowVSM$(sampler2D shadowMap, vec3 shadowParams, float exponent) {\n    return VSM$(shadowMap, dShadowCoord.xy, shadowParams.x, dShadowCoord.z, shadowParams.y, exponent);\n}\nfloat getShadowSpotVSM$(sampler2D shadowMap, vec4 shadowParams, float exponent) {\n    return VSM$(shadowMap, dShadowCoord.xy, shadowParams.x, length(dLightDirW) * shadowParams.w + shadowParams.z, shadowParams.y, exponent);\n}\n";
pc.shaderChunks.shadowEVSMnPS = "float VSM$(sampler2D tex, vec2 texCoords, float resolution, float Z, float vsmBias, float exponent) {\n    float pixelSize = 1.0 / resolution;\n    texCoords -= vec2(pixelSize);\n    vec3 s00 = texture2D(tex, texCoords).xyz;\n    vec3 s10 = texture2D(tex, texCoords + vec2(pixelSize, 0)).xyz;\n    vec3 s01 = texture2D(tex, texCoords + vec2(0, pixelSize)).xyz;\n    vec3 s11 = texture2D(tex, texCoords + vec2(pixelSize)).xyz;\n    vec2 fr = fract(texCoords * resolution);\n    vec3 h0 = mix(s00, s10, fr.x);\n    vec3 h1 = mix(s01, s11, fr.x);\n    vec3 moments = mix(h0, h1, fr.y);\n    return calculateEVSM(moments, Z, vsmBias, exponent);\n}\nfloat getShadowVSM$(sampler2D shadowMap, vec3 shadowParams, float exponent) {\n    return VSM$(shadowMap, dShadowCoord.xy, shadowParams.x, dShadowCoord.z, shadowParams.y, exponent);\n}\nfloat getShadowSpotVSM$(sampler2D shadowMap, vec4 shadowParams, float exponent) {\n    return VSM$(shadowMap, dShadowCoord.xy, shadowParams.x, length(dLightDirW) * shadowParams.w + shadowParams.z, shadowParams.y, exponent);\n}\n";
pc.shaderChunks.shadowStandardPS = "float unpackFloat(vec4 rgbaDepth) {\n    const vec4 bitShift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);\n    return dot(rgbaDepth, bitShift);\n}\nvec3 lessThan2(vec3 a, vec3 b) {\n    return clamp((b - a)*1000.0, 0.0, 1.0); // softer version\n}\n// ----- Direct/Spot Sampling -----\nfloat getShadowHard(sampler2D shadowMap, vec3 shadowParams) {\n    float depth = unpackFloat(texture2D(shadowMap, dShadowCoord.xy));\n    return (depth < dShadowCoord.z) ? 0.0 : 1.0;\n}\nfloat getShadowSpotHard(sampler2D shadowMap, vec4 shadowParams) {\n    float depth = unpackFloat(texture2D(shadowMap, dShadowCoord.xy));\n    return (depth < (length(dLightDirW) * shadowParams.w + shadowParams.z)) ? 0.0 : 1.0;\n}\nfloat _xgetShadowPCF3x3(mat3 depthKernel, sampler2D shadowMap, vec3 shadowParams) {\n    mat3 shadowKernel;\n    vec3 shadowCoord = dShadowCoord;\n    vec3 shadowZ = vec3(shadowCoord.z);\n    shadowKernel[0] = vec3(greaterThan(depthKernel[0], shadowZ));\n    shadowKernel[1] = vec3(greaterThan(depthKernel[1], shadowZ));\n    shadowKernel[2] = vec3(greaterThan(depthKernel[2], shadowZ));\n    vec2 fractionalCoord = fract( shadowCoord.xy * shadowParams.x );\n    shadowKernel[0] = mix(shadowKernel[0], shadowKernel[1], fractionalCoord.x);\n    shadowKernel[1] = mix(shadowKernel[1], shadowKernel[2], fractionalCoord.x);\n    vec4 shadowValues;\n    shadowValues.x = mix(shadowKernel[0][0], shadowKernel[0][1], fractionalCoord.y);\n    shadowValues.y = mix(shadowKernel[0][1], shadowKernel[0][2], fractionalCoord.y);\n    shadowValues.z = mix(shadowKernel[1][0], shadowKernel[1][1], fractionalCoord.y);\n    shadowValues.w = mix(shadowKernel[1][1], shadowKernel[1][2], fractionalCoord.y);\n    return dot( shadowValues, vec4( 1.0 ) ) * 0.25;\n}\nfloat _getShadowPCF3x3(sampler2D shadowMap, vec3 shadowParams) {\n    vec3 shadowCoord = dShadowCoord;\n    float xoffset = 1.0 / shadowParams.x; // 1/shadow map width\n    float dx0 = -xoffset;\n    float dx1 = xoffset;\n    mat3 depthKernel;\n    depthKernel[0][0] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx0, dx0)));\n    depthKernel[0][1] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx0, 0.0)));\n    depthKernel[0][2] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx0, dx1)));\n    depthKernel[1][0] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(0.0, dx0)));\n    depthKernel[1][1] = unpackFloat(texture2D(shadowMap, shadowCoord.xy));\n    depthKernel[1][2] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(0.0, dx1)));\n    depthKernel[2][0] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx1, dx0)));\n    depthKernel[2][1] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx1, 0.0)));\n    depthKernel[2][2] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx1, dx1)));\n    return _xgetShadowPCF3x3(depthKernel, shadowMap, shadowParams);\n}\nfloat getShadowPCF3x3(sampler2D shadowMap, vec3 shadowParams) {\n    return _getShadowPCF3x3(shadowMap, shadowParams);\n}\nfloat getShadowSpotPCF3x3(sampler2D shadowMap, vec4 shadowParams) {\n    return _getShadowPCF3x3(shadowMap, shadowParams.xyz);\n}\n// ----- Point Sampling -----\nfloat getShadowPointHard(samplerCube shadowMap, vec4 shadowParams) {\n    float depth = unpackFloat(textureCube(shadowMap, dLightDirNormW));\n    return float(depth > length(dLightDirW) * shadowParams.w + shadowParams.z);\n}\nfloat _getShadowPoint(samplerCube shadowMap, vec4 shadowParams, vec3 dir) {\n    vec3 tc = normalize(dir);\n    vec3 tcAbs = abs(tc);\n    vec4 dirX = vec4(1,0,0, tc.x);\n    vec4 dirY = vec4(0,1,0, tc.y);\n    float majorAxisLength = tc.z;\n    if ((tcAbs.x > tcAbs.y) && (tcAbs.x > tcAbs.z)) {\n        dirX = vec4(0,0,1, tc.z);\n        dirY = vec4(0,1,0, tc.y);\n        majorAxisLength = tc.x;\n    } else if ((tcAbs.y > tcAbs.x) && (tcAbs.y > tcAbs.z)) {\n        dirX = vec4(1,0,0, tc.x);\n        dirY = vec4(0,0,1, tc.z);\n        majorAxisLength = tc.y;\n    }\n    float shadowParamsInFaceSpace = ((1.0/shadowParams.x) * 2.0) * abs(majorAxisLength);\n    vec3 xoffset = (dirX.xyz * shadowParamsInFaceSpace);\n    vec3 yoffset = (dirY.xyz * shadowParamsInFaceSpace);\n    vec3 dx0 = -xoffset;\n    vec3 dy0 = -yoffset;\n    vec3 dx1 = xoffset;\n    vec3 dy1 = yoffset;\n    mat3 shadowKernel;\n    mat3 depthKernel;\n    depthKernel[0][0] = unpackFloat(textureCube(shadowMap, tc + dx0 + dy0));\n    depthKernel[0][1] = unpackFloat(textureCube(shadowMap, tc + dx0));\n    depthKernel[0][2] = unpackFloat(textureCube(shadowMap, tc + dx0 + dy1));\n    depthKernel[1][0] = unpackFloat(textureCube(shadowMap, tc + dy0));\n    depthKernel[1][1] = unpackFloat(textureCube(shadowMap, tc));\n    depthKernel[1][2] = unpackFloat(textureCube(shadowMap, tc + dy1));\n    depthKernel[2][0] = unpackFloat(textureCube(shadowMap, tc + dx1 + dy0));\n    depthKernel[2][1] = unpackFloat(textureCube(shadowMap, tc + dx1));\n    depthKernel[2][2] = unpackFloat(textureCube(shadowMap, tc + dx1 + dy1));\n    vec3 shadowZ = vec3(length(dir) * shadowParams.w + shadowParams.z);\n    shadowKernel[0] = vec3(lessThan2(depthKernel[0], shadowZ));\n    shadowKernel[1] = vec3(lessThan2(depthKernel[1], shadowZ));\n    shadowKernel[2] = vec3(lessThan2(depthKernel[2], shadowZ));\n    vec2 uv = (vec2(dirX.w, dirY.w) / abs(majorAxisLength)) * 0.5;\n    vec2 fractionalCoord = fract( uv * shadowParams.x );\n    shadowKernel[0] = mix(shadowKernel[0], shadowKernel[1], fractionalCoord.x);\n    shadowKernel[1] = mix(shadowKernel[1], shadowKernel[2], fractionalCoord.x);\n    vec4 shadowValues;\n    shadowValues.x = mix(shadowKernel[0][0], shadowKernel[0][1], fractionalCoord.y);\n    shadowValues.y = mix(shadowKernel[0][1], shadowKernel[0][2], fractionalCoord.y);\n    shadowValues.z = mix(shadowKernel[1][0], shadowKernel[1][1], fractionalCoord.y);\n    shadowValues.w = mix(shadowKernel[1][1], shadowKernel[1][2], fractionalCoord.y);\n    return 1.0 - dot( shadowValues, vec4( 1.0 ) ) * 0.25;\n}\nfloat getShadowPointPCF3x3(samplerCube shadowMap, vec4 shadowParams) {\n    return _getShadowPoint(shadowMap, shadowParams, dLightDirW);\n}\n";
pc.shaderChunks.shadowStandardVSPS = "float getShadowHardVS(sampler2D shadowMap, vec3 shadowParams) {\n    float depth = unpackFloat(texture2DProj(shadowMap, vMainShadowUv));\n    return (depth < min(vMainShadowUv.z + shadowParams.z, 1.0)) ? 0.0 : 1.0;\n}\nfloat getShadowPCF3x3VS(sampler2D shadowMap, vec3 shadowParams) {\n    dShadowCoord = vMainShadowUv.xyz;\n    dShadowCoord.z += getShadowBias(shadowParams.x, shadowParams.z);\n    dShadowCoord.xyz /= vMainShadowUv.w;\n    dShadowCoord.z = min(dShadowCoord.z, 1.0);\n    return _getShadowPCF3x3(shadowMap, shadowParams);\n}\n";
pc.shaderChunks.shadowVSM8PS = "float calculateVSM8(vec3 moments, float Z, float vsmBias) {\n    float VSMBias = vsmBias;//0.01 * 0.25;\n    float depthScale = VSMBias * Z;\n    float minVariance1 = depthScale * depthScale;\n    return chebyshevUpperBound(moments.xy, Z, minVariance1, 0.1);\n}\nfloat decodeFloatRG(vec2 rg) {\n    return rg.y*(1.0/255.0) + rg.x;\n}\nfloat VSM8(sampler2D tex, vec2 texCoords, float resolution, float Z, float vsmBias, float exponent) {\n    vec4 c = texture2D(tex, texCoords);\n    vec3 moments = vec3(decodeFloatRG(c.xy), decodeFloatRG(c.zw), 0.0);\n    return calculateVSM8(moments, Z, vsmBias);\n}\nfloat getShadowVSM8(sampler2D shadowMap, vec3 shadowParams, float exponent) {\n    return VSM8(shadowMap, dShadowCoord.xy, shadowParams.x, dShadowCoord.z, shadowParams.y, 0.0);\n}\nfloat getShadowSpotVSM8(sampler2D shadowMap, vec4 shadowParams, float exponent) {\n    return VSM8(shadowMap, dShadowCoord.xy, shadowParams.x, length(dLightDirW) * shadowParams.w + shadowParams.z, shadowParams.y, 0.0);\n}\n";
pc.shaderChunks.shadowVSMVSPS = "float getShadowVSM$VS(sampler2D shadowMap, vec3 shadowParams, float exponent) {\n    dShadowCoord = vMainShadowUv.xyz;\n    dShadowCoord.z += shadowParams.z;\n    dShadowCoord.xyz /= vMainShadowUv.w;\n    dShadowCoord.z = min(dShadowCoord.z, 1.0);\n    return $VSM(shadowMap, dShadowCoord.xy, shadowParams.x, dShadowCoord.z, shadowParams.y, exponent);\n}\n";
pc.shaderChunks.shadowVSM_commonPS = "float linstep(float a, float b, float v) {\n    return saturate((v - a) / (b - a));\n}\nfloat reduceLightBleeding(float pMax, float amount) {\n  // Remove the [0, amount] tail and linearly rescale (amount, 1].\n   return linstep(amount, 1.0, pMax);\n}\nfloat chebyshevUpperBound(vec2 moments, float mean, float minVariance, float lightBleedingReduction) {\n    // Compute variance\n    float variance = moments.y - (moments.x * moments.x);\n    variance = max(variance, minVariance);\n    // Compute probabilistic upper bound\n    float d = mean - moments.x;\n    float pMax = variance / (variance + (d * d));\n    pMax = reduceLightBleeding(pMax, lightBleedingReduction);\n    // One-tailed Chebyshev\n    return (mean <= moments.x ? 1.0 : pMax);\n}\nfloat calculateEVSM(vec3 moments, float Z, float vsmBias, float exponent) {\n    Z = 2.0 * Z - 1.0;\n    float warpedDepth = exp(exponent * Z);\n    moments.xy += vec2(warpedDepth, warpedDepth*warpedDepth) * (1.0 - moments.z);\n    float VSMBias = vsmBias;//0.01 * 0.25;\n    float depthScale = VSMBias * exponent * warpedDepth;\n    float minVariance1 = depthScale * depthScale;\n    return chebyshevUpperBound(moments.xy, warpedDepth, minVariance1, 0.1);\n}\n";
pc.shaderChunks.skinConstVS = "attribute vec4 vertex_boneWeights;\nattribute vec4 vertex_boneIndices;\nuniform mat4 matrix_pose[BONE_LIMIT];\nuniform vec3 skinPosOffset;\nmat4 getBoneMatrix(const in float i)\n{\n    mat4 bone = matrix_pose[int(i)];\n    return bone;\n}\n";
pc.shaderChunks.skinTexVS = "attribute vec4 vertex_boneWeights;\nattribute vec4 vertex_boneIndices;\nuniform sampler2D texture_poseMap;\nuniform vec2 texture_poseMapSize;\nuniform vec3 skinPosOffset;\nmat4 getBoneMatrix(const in float i)\n{\n    float j = i * 4.0;\n    float x = mod(j, float(texture_poseMapSize.x));\n    float y = floor(j / float(texture_poseMapSize.x));\n    float dx = 1.0 / float(texture_poseMapSize.x);\n    float dy = 1.0 / float(texture_poseMapSize.y);\n    y = dy * (y + 0.5);\n    vec4 v1 = texture2D(texture_poseMap, vec2(dx * (x + 0.5), y));\n    vec4 v2 = texture2D(texture_poseMap, vec2(dx * (x + 1.5), y));\n    vec4 v3 = texture2D(texture_poseMap, vec2(dx * (x + 2.5), y));\n    vec4 v4 = texture2D(texture_poseMap, vec2(dx * (x + 3.5), y));\n    mat4 bone = mat4(v1, v2, v3, v4);\n    return bone;\n}\n";
pc.shaderChunks.skyboxPS = "varying vec3 vViewDir;\nuniform samplerCube texture_cubeMap;\nvoid main(void) {\n    gl_FragColor = textureCube(texture_cubeMap, fixSeams(vViewDir));\n}\n";
pc.shaderChunks.skyboxVS = "attribute vec3 aPosition;\nuniform mat4 matrix_view;\nuniform mat4 matrix_projection;\nvarying vec3 vViewDir;\nvoid main(void)\n{\n    mat4 view = matrix_view;\n    view[3][0] = view[3][1] = view[3][2] = 0.0;\n    gl_Position = matrix_projection * view * vec4(aPosition, 1.0);\n    // Force skybox to far Z, regardless of the clip planes on the camera\n    // Subtract a tiny fudge factor to ensure floating point errors don't\n    // still push pixels beyond far Z. See:\n    // http://www.opengl.org/discussion_boards/showthread.php/171867-skybox-problem\n    gl_Position.z = gl_Position.w - 0.00001;\n    vViewDir = aPosition;\n    vViewDir.x *= -1.0;\n}\n";
pc.shaderChunks.skyboxHDRPS = "varying vec3 vViewDir;\nuniform samplerCube texture_cubeMap;\nvoid main(void) {\n    vec3 color = processEnvironment($textureCubeSAMPLE(texture_cubeMap, fixSeamsStatic(vViewDir, $FIXCONST)).rgb);\n    color = toneMap(color);\n    color = gammaCorrectOutput(color);\n    gl_FragColor = vec4(color, 1.0);\n}\n";
pc.shaderChunks.skyboxPrefilteredCubePS = "varying vec3 vViewDir;\nuniform samplerCube texture_cubeMap;\nvec3 fixSeamsStretch(vec3 vec, float mipmapIndex, float cubemapSize) {\n    float scale = 1.0 - exp2(mipmapIndex) / cubemapSize;\n    float M = max(max(abs(vec.x), abs(vec.y)), abs(vec.z));\n    if (abs(vec.x) != M) vec.x *= scale;\n    if (abs(vec.y) != M) vec.y *= scale;\n    if (abs(vec.z) != M) vec.z *= scale;\n    return vec;\n}\nvoid main(void) {\n    vec3 color = textureCubeRGBM(texture_cubeMap, fixSeamsStretch(vViewDir, 0.0, 128.0));\n    color = toneMap(color);\n    color = gammaCorrectOutput(color);\n    gl_FragColor = vec4(color, 1.0);\n}\n";
pc.shaderChunks.specularAaNonePS = "float antiAliasGlossiness(float power) {\n    return power;\n}\n";
pc.shaderChunks.specularAaToksvigPS = "float antiAliasGlossiness(float power) {\n    float rlen = 1.0 / saturate(length(dNormalMap));\n    float toksvig = 1.0 / (1.0 + power * (rlen - 1.0));\n    return power * toksvig;\n}\n";
pc.shaderChunks.specularAaToksvigFloatPS = "float antiAliasGlossiness(float power) {\n    float rlen = 1.0 / saturate(length(dNormalMap));\n    float toksvig = 1.0 / (1.0 + power * (rlen - 1.0));\n    return power * mix(1.0, toksvig, material_bumpiness);\n}\n";
pc.shaderChunks.specularConstPS = "uniform vec3 material_specular;\nvoid getSpecularity() {\n    dSpecularity = material_specular;\n}\n";
pc.shaderChunks.specularTexPS = "uniform sampler2D texture_specularMap;\nvoid getSpecularity() {\n    dSpecularity = texture2D(texture_specularMap, $UV).$CH;\n}\n";
pc.shaderChunks.specularTexConstPS = "uniform sampler2D texture_specularMap;\nuniform vec3 material_specular;\nvoid getSpecularity() {\n    dSpecularity = texture2D(texture_specularMap, $UV).$CH * material_specular;\n}\n";
pc.shaderChunks.specularVertPS = "void getSpecularity() {\n    dSpecularity = saturate(vVertexColor.$CH);\n}\n";
pc.shaderChunks.specularVertConstPS = "uniform vec3 material_specular;\nvoid getSpecularity() {\n    dSpecularity = saturate(vVertexColor.$CH) * material_specular;\n}\n";
pc.shaderChunks.spotPS = "float getSpotEffect(vec3 lightSpotDirW, float lightInnerConeAngle, float lightOuterConeAngle) {\n    float cosAngle = dot(dLightDirNormW, lightSpotDirW);\n    return smoothstep(lightOuterConeAngle, lightInnerConeAngle, cosAngle);\n}\n";
pc.shaderChunks.startPS = "\nvoid main(void) {\n\tdDiffuseLight = vec3(0);\n\tdSpecularLight = vec3(0);\n    dReflection = vec4(0);\n    dSpecularity = vec3(0);\n";
pc.shaderChunks.startVS = "\nvoid main(void) {\n    gl_Position = getPosition();\n";
pc.shaderChunks.storeEVSMPS = "float exponent = VSM_EXPONENT;\ndepth = 2.0 * depth - 1.0;\ndepth =  exp(exponent * depth);\ngl_FragColor = vec4(depth, depth*depth, 1.0, 1.0);\n";
pc.shaderChunks.tangentBinormalVS = "\nvec3 getTangent() {\n    return normalize(dNormalMatrix * vertex_tangent.xyz);\n}\nvec3 getBinormal() {\n    return cross(vNormalW, vTangentW) * vertex_tangent.w;\n}\n";
pc.shaderChunks.tonemappingAcesPS = "uniform float exposure;\nvec3 toneMap(vec3 color) {\n    float tA = 2.51;\n    float tB = 0.03;\n    float tC = 2.43;\n    float tD = 0.59;\n    float tE = 0.14;\n    vec3 x = color * exposure;\n    return (x*(tA*x+tB))/(x*(tC*x+tD)+tE);\n}\n";
pc.shaderChunks.tonemappingAces2PS = "uniform float exposure;\n// ACES approximation by Stephen Hill\n// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT\nconst mat3 ACESInputMat = mat3(\n    0.59719, 0.35458, 0.04823,\n    0.07600, 0.90834, 0.01566,\n    0.02840, 0.13383, 0.83777\n);\n// ODT_SAT => XYZ => D60_2_D65 => sRGB\nconst mat3 ACESOutputMat = mat3(\n     1.60475, -0.53108, -0.07367,\n    -0.10208,  1.10813, -0.00605,\n    -0.00327, -0.07276,  1.07602\n);\nvec3 RRTAndODTFit(vec3 v) {\n    vec3 a = v * (v + 0.0245786) - 0.000090537;\n    vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n    return a / b;\n}\nvec3 toneMap(vec3 color) {\n    color *= exposure;\n    color = color * ACESInputMat;\n    // Apply RRT and ODT\n    color = RRTAndODTFit(color);\n    color = color * ACESOutputMat;\n    // Clamp to [0, 1]\n    color = clamp(color, 0.0, 1.0);\n    return color;\n}\n";
pc.shaderChunks.tonemappingFilmicPS = "const float A =  0.15;\nconst float B =  0.50;\nconst float C =  0.10;\nconst float D =  0.20;\nconst float E =  0.02;\nconst float F =  0.30;\nconst float W =  11.2;\nuniform float exposure;\nvec3 uncharted2Tonemap(vec3 x) {\n   return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;\n}\nvec3 toneMap(vec3 color) {\n    color = uncharted2Tonemap(color * exposure);\n    vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W,W,W));\n    color = color * whiteScale;\n    return color;\n}\n";
pc.shaderChunks.tonemappingHejlPS = "uniform float exposure;\nvec3 toneMap(vec3 color) {\n    color *= exposure;\n    const float  A = 0.22, B = 0.3, C = .1, D = 0.2, E = .01, F = 0.3;\n    const float Scl = 1.25;\n    vec3 h = max( vec3(0.0), color - vec3(0.004) );\n    return (h*((Scl*A)*h+Scl*vec3(C*B,C*B,C*B))+Scl*vec3(D*E,D*E,D*E)) / (h*(A*h+vec3(B,B,B))+vec3(D*F,D*F,D*F)) - Scl*vec3(E/F,E/F,E/F);\n}\n";
pc.shaderChunks.tonemappingLinearPS = "uniform float exposure;\nvec3 toneMap(vec3 color) {\n    return color * exposure;\n}\n";
pc.shaderChunks.tonemappingNonePS = "vec3 toneMap(vec3 color) {\n    return color;\n}\n";
pc.shaderChunks.transformVS = "mat4 getModelMatrix() {\n    return matrix_model;\n}\nvec4 getPosition() {\n    dModelMatrix = getModelMatrix();\n    vec4 posW = dModelMatrix * vec4(vertex_position, 1.0);\n    dPositionW = posW.xyz;\n    return matrix_viewProjection * posW;\n}\nvec3 getWorldPosition() {\n    return dPositionW;\n}\n";
pc.shaderChunks.transformDeclVS = "attribute vec3 vertex_position;\nuniform mat4 matrix_model;\nuniform mat4 matrix_viewProjection;\nvec3 dPositionW;\nmat4 dModelMatrix;\n";
pc.shaderChunks.transformInstancedVS = "mat4 getModelMatrix() {\n    return mat4(instance_line1, instance_line2, instance_line3, instance_line4);\n}\nvec4 getPosition() {\n    dModelMatrix = getModelMatrix();\n    vec4 posW = dModelMatrix * vec4(vertex_position, 1.0);\n    dPositionW = posW.xyz;\n    return matrix_viewProjection * posW;\n}\nvec3 getWorldPosition() {\n    return dPositionW;\n}\n";
pc.shaderChunks.transformScreenSpaceVS = "mat4 getModelMatrix() {\n    return matrix_model;\n}\nvec4 getPosition() {\n    vec4 posW = vec4((getModelMatrix() * vec4(vertex_position, 1.0)).xy, 0.0, 1.0);\n    dPositionW = posW.xyz;\n    return posW;\n}\nvec3 getWorldPosition() {\n    return dPositionW;\n}\n";
pc.shaderChunks.transformSkinnedVS = "mat4 getModelMatrix() {\n    return getBoneMatrix(vertex_boneIndices.x) * vertex_boneWeights.x +\n           getBoneMatrix(vertex_boneIndices.y) * vertex_boneWeights.y +\n           getBoneMatrix(vertex_boneIndices.z) * vertex_boneWeights.z +\n           getBoneMatrix(vertex_boneIndices.w) * vertex_boneWeights.w;\n}\nvec4 getPosition() {\n    dModelMatrix = getModelMatrix();\n    vec4 posW = dModelMatrix * vec4(vertex_position, 1.0);\n    //posW.xyz /= posW.w;\n    posW.xyz += skinPosOffset;\n    dPositionW = posW.xyz;// / posW.w;\n    return matrix_viewProjection * posW;\n}\nvec3 getWorldPosition() {\n    return dPositionW;\n}\n";
pc.shaderChunks.transformUv1VS = "mat4 getModelMatrix() {\n    return matrix_model;\n}\nvec4 getPosition() {\n    dModelMatrix = getModelMatrix();\n    vec4 posW = dModelMatrix * vec4(vertex_position, 1.0);\n    dPositionW = posW.xyz;\n    return vec4(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1);\n}\nvec3 getWorldPosition() {\n    return dPositionW;\n}\n";
pc.shaderChunks.uv0VS = "\nvec2 getUv0() {\n    return vertex_texCoord0;\n}\n";
pc.shaderChunks.uv1VS = "\nvec2 getUv1() {\n    return vertex_texCoord1;\n}\n";
pc.shaderChunks.viewDirPS = "void getViewDir() {\n    dViewDirW = normalize(view_position - vPositionW);\n}\n";
pc.shaderChunks.viewNormalVS = "\nuniform mat4 matrix_view;\nvec3 getViewNormal() {\n    return mat3(matrix_view) * vNormalW;\n}\n";
pc.programlib = {gammaCode:function(value) {
  return value === pc.GAMMA_NONE ? pc.shaderChunks.gamma1_0PS : value === pc.GAMMA_SRGBFAST ? pc.shaderChunks.gamma2_2FastPS : pc.shaderChunks.gamma2_2PS;
}, tonemapCode:function(value) {
  if (value === pc.TONEMAP_FILMIC) {
    return pc.shaderChunks.tonemappingFilmicPS;
  } else {
    if (value === pc.TONEMAP_LINEAR) {
      return pc.shaderChunks.tonemappingLinearPS;
    } else {
      if (value === pc.TONEMAP_HEJL) {
        return pc.shaderChunks.tonemappingHejlPS;
      } else {
        if (value === pc.TONEMAP_ACES) {
          return pc.shaderChunks.tonemappingAcesPS;
        } else {
          if (value === pc.TONEMAP_ACES2) {
            return pc.shaderChunks.tonemappingAces2PS;
          }
        }
      }
    }
  }
  return pc.shaderChunks.tonemappingNonePS;
}, fogCode:function(value) {
  if (value === "linear") {
    return pc.shaderChunks.fogLinearPS;
  } else {
    if (value === "exp") {
      return pc.shaderChunks.fogExpPS;
    } else {
      if (value === "exp2") {
        return pc.shaderChunks.fogExp2PS;
      } else {
        return pc.shaderChunks.fogNonePS;
      }
    }
  }
}, skinCode:function(device) {
  if (device.supportsBoneTextures) {
    return pc.shaderChunks.skinTexVS;
  } else {
    return "#define BONE_LIMIT " + device.getBoneLimit() + "\n" + pc.shaderChunks.skinConstVS;
  }
}, precisionCode:function(device) {
  return "precision " + device.precision + " float;\n\n";
}, begin:function() {
  return "void main(void)\n{\n";
}, end:function() {
  return "}\n";
}};
pc.programlib.basic = {generateKey:function(device, options) {
  var key = "basic";
  if (options.fog) {
    key += "_fog";
  }
  if (options.alphaTest) {
    key += "_atst";
  }
  if (options.vertexColors) {
    key += "_vcol";
  }
  if (options.diffuseMap) {
    key += "_diff";
  }
  return key;
}, createShaderDefinition:function(device, options) {
  var attributes = {vertex_position:pc.SEMANTIC_POSITION};
  if (options.skin) {
    attributes.vertex_boneWeights = pc.SEMANTIC_BLENDWEIGHT;
    attributes.vertex_boneIndices = pc.SEMANTIC_BLENDINDICES;
  }
  if (options.vertexColors) {
    attributes.vertex_color = pc.SEMANTIC_COLOR;
  }
  if (options.diffuseMap) {
    attributes.vertex_texCoord0 = pc.SEMANTIC_TEXCOORD0;
  }
  var chunks = pc.shaderChunks;
  var code = "";
  code += chunks.transformDeclVS;
  if (options.skin) {
    code += pc.programlib.skinCode(device);
    code += chunks.transformSkinnedVS;
  } else {
    code += chunks.transformVS;
  }
  if (options.vertexColors) {
    code += "attribute vec4 vertex_color;\n";
    code += "varying vec4 vColor;\n";
  }
  if (options.diffuseMap) {
    code += "attribute vec2 vertex_texCoord0;\n";
    code += "varying vec2 vUv0;\n";
  }
  code += pc.programlib.begin();
  code += "   gl_Position = getPosition();\n";
  if (options.vertexColors) {
    code += "    vColor = vertex_color;\n";
  }
  if (options.diffuseMap) {
    code += "    vUv0 = vertex_texCoord0;\n";
  }
  code += pc.programlib.end();
  var vshader = code;
  code = pc.programlib.precisionCode(device);
  if (options.vertexColors) {
    code += "varying vec4 vColor;\n";
  } else {
    code += "uniform vec4 uColor;\n";
  }
  if (options.diffuseMap) {
    code += "varying vec2 vUv0;\n";
    code += "uniform sampler2D texture_diffuseMap;\n";
  }
  if (options.fog) {
    code += pc.programlib.fogCode(options.fog);
  }
  if (options.alphatest) {
    code += chunks.alphaTestPS;
  }
  code += pc.programlib.begin();
  if (options.vertexColors) {
    code += "    gl_FragColor = vColor;\n";
  } else {
    code += "    gl_FragColor = uColor;\n";
  }
  if (options.diffuseMap) {
    code += "    gl_FragColor *= texture2D(texture_diffuseMap, vUv0);\n";
  }
  if (options.alphatest) {
    code += "   alphaTest(gl_FragColor.a);\n";
  }
  if (options.fog) {
    code += "   glFragColor.rgb = addFog(gl_FragColor.rgb);\n";
  }
  code += pc.programlib.end();
  var fshader = code;
  return {attributes:attributes, vshader:vshader, fshader:fshader};
}};
pc.programlib.depth = {generateKey:function(device, options) {
  var key = "depth";
  if (options.skin) {
    key += "_skin";
  }
  if (options.opacityMap) {
    key += "_opam";
  }
  if (options.instancing) {
    key += "_inst";
  }
  return key;
}, createShaderDefinition:function(device, options) {
  var attributes = {vertex_position:pc.SEMANTIC_POSITION};
  if (options.skin) {
    attributes.vertex_boneWeights = pc.SEMANTIC_BLENDWEIGHT;
    attributes.vertex_boneIndices = pc.SEMANTIC_BLENDINDICES;
  }
  if (options.opacityMap) {
    attributes.vertex_texCoord0 = pc.SEMANTIC_TEXCOORD0;
  }
  var chunks = pc.shaderChunks;
  var code = "";
  code += chunks.transformDeclVS;
  if (options.skin) {
    code += pc.programlib.skinCode(device);
    code += chunks.transformSkinnedVS;
  } else {
    if (options.instancing) {
      attributes.instance_line1 = pc.SEMANTIC_TEXCOORD2;
      attributes.instance_line2 = pc.SEMANTIC_TEXCOORD3;
      attributes.instance_line3 = pc.SEMANTIC_TEXCOORD4;
      attributes.instance_line4 = pc.SEMANTIC_TEXCOORD5;
      code += chunks.instancingVS;
      code += chunks.transformInstancedVS;
    } else {
      if (options.screenSpace) {
        code += chunks.transformScreenSpaceVS;
      } else {
        code += chunks.transformVS;
      }
    }
  }
  if (options.opacityMap) {
    code += "attribute vec2 vertex_texCoord0;\n\n";
    code += "varying vec2 vUv0;\n\n";
  }
  code += "varying float vDepth;\n";
  code += "uniform mat4 matrix_view;\n";
  code += "uniform float camera_far;\n\n";
  code += pc.programlib.begin();
  code += "   gl_Position = getPosition();\n";
  if (options.opacityMap) {
    code += "    vUv0 = vertex_texCoord0;\n";
  }
  code += "    vDepth = -(matrix_view * vec4(getWorldPosition(),1.0)).z / camera_far;\n";
  code += pc.programlib.end();
  var vshader = code;
  code = pc.programlib.precisionCode(device);
  if (options.opacityMap) {
    code += "varying vec2 vUv0;\n\n";
    code += "uniform sampler2D texture_opacityMap;\n\n";
    code += chunks.alphaTestPS;
  }
  code += "varying float vDepth;\n\n";
  code += "vec4 packFloat(float depth)\n";
  code += "{\n";
  code += "    const vec4 bit_shift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);\n";
  code += "    const vec4 bit_mask  = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\n";
  code += "    vec4 res = mod(depth * bit_shift * vec4(255), vec4(256) ) / vec4(255);\n";
  code += "    res -= res.xxyz * bit_mask;\n";
  code += "    return res;\n";
  code += "}\n\n";
  code += pc.programlib.begin();
  if (options.opacityMap) {
    code += "    alphaTest(texture2D(texture_opacityMap, vUv0)." + options.opacityChannel[0] + " );\n\n";
  }
  code += "float depth = vDepth;\n";
  code += "gl_FragColor = packFloat(depth);\n";
  code += pc.programlib.end();
  var fshader = code;
  return {attributes:attributes, vshader:vshader, fshader:fshader};
}};
pc.programlib.depthrgba = {generateKey:function(device, options) {
  var key = "depthrgba";
  if (options.skin) {
    key += "_skin";
  }
  if (options.opacityMap) {
    key += "_opam" + options.opacityChannel;
  }
  if (options.point) {
    key += "_pnt";
  }
  if (options.instancing) {
    key += "_inst";
  }
  key += "_" + options.shadowType;
  return key;
}, createShaderDefinition:function(device, options) {
  var attributes = {vertex_position:pc.SEMANTIC_POSITION};
  if (options.skin) {
    attributes.vertex_boneWeights = pc.SEMANTIC_BLENDWEIGHT;
    attributes.vertex_boneIndices = pc.SEMANTIC_BLENDINDICES;
  }
  if (options.opacityMap) {
    attributes.vertex_texCoord0 = pc.SEMANTIC_TEXCOORD0;
  }
  var chunks = pc.shaderChunks;
  var code = "";
  code += chunks.transformDeclVS;
  if (options.skin) {
    code += pc.programlib.skinCode(device);
    code += chunks.transformSkinnedVS;
  } else {
    if (options.instancing) {
      attributes.instance_line1 = pc.SEMANTIC_TEXCOORD2;
      attributes.instance_line2 = pc.SEMANTIC_TEXCOORD3;
      attributes.instance_line3 = pc.SEMANTIC_TEXCOORD4;
      attributes.instance_line4 = pc.SEMANTIC_TEXCOORD5;
      code += chunks.instancingVS;
      code += chunks.transformInstancedVS;
    } else {
      if (options.screenSpace) {
        code += chunks.transformScreenSpaceVS;
      } else {
        code += chunks.transformVS;
      }
    }
  }
  if (options.opacityMap) {
    code += "attribute vec2 vertex_texCoord0;\n\n";
    code += "varying vec2 vUv0;\n\n";
  }
  if (options.point) {
    code += "varying vec3 worldPos;\n\n";
  }
  code += pc.programlib.begin();
  code += "   gl_Position = getPosition();\n";
  if (options.opacityMap) {
    code += "    vUv0 = vertex_texCoord0;\n";
  }
  if (options.point) {
    code += "    worldPos = dPositionW;\n";
  }
  code += pc.programlib.end();
  var vshader = code;
  code = pc.programlib.precisionCode(device);
  if (options.shadowType === pc.SHADOW_VSM32) {
    if (device.extTextureFloatHighPrecision) {
      code += "#define VSM_EXPONENT 15.0\n\n";
    } else {
      code += "#define VSM_EXPONENT 5.54\n\n";
    }
  } else {
    if (options.shadowType === pc.SHADOW_VSM16) {
      code += "#define VSM_EXPONENT 5.54\n\n";
    }
  }
  if (options.opacityMap) {
    code += "varying vec2 vUv0;\n\n";
    code += "uniform sampler2D texture_opacityMap;\n\n";
    code += chunks.alphaTestPS;
  }
  if (options.point) {
    code += "varying vec3 worldPos;\n\n";
    code += "uniform vec3 view_position;\n\n";
    code += "uniform float light_radius;\n\n";
  }
  if (options.shadowType === pc.SHADOW_DEPTH) {
    code += chunks.packDepthPS;
  } else {
    if (options.shadowType === pc.SHADOW_VSM8) {
      code += "vec2 encodeFloatRG( float v ) {\n\r\n                     vec2 enc = vec2(1.0, 255.0) * v;\n\r\n                     enc = fract(enc);\n\r\n                     enc -= enc.yy * vec2(1.0/255.0, 1.0/255.0);\n\r\n                     return enc;\n\r\n                    }\n";
    }
  }
  code += pc.programlib.begin();
  if (options.opacityMap) {
    code += "    alphaTest( texture2D(texture_opacityMap, vUv0)." + options.opacityChannel + " );\n\n";
  }
  if (options.point) {
    code += "   float depth = min(distance(view_position, worldPos) / light_radius, 0.99999);\n";
  } else {
    code += "   float depth = gl_FragCoord.z;\n";
  }
  if (options.shadowType === pc.SHADOW_DEPTH) {
    code += "   gl_FragData[0] = packFloat(depth);\n";
  } else {
    if (options.shadowType === pc.SHADOW_VSM8) {
      code += "   gl_FragColor = vec4(encodeFloatRG(depth), encodeFloatRG(depth*depth));\n";
    } else {
      code += chunks.storeEVSMPS;
    }
  }
  code += pc.programlib.end();
  var fshader = code;
  return {attributes:attributes, vshader:vshader, fshader:fshader};
}};
pc.programlib.particle = {generateKey:function(device, options) {
  var key = "particle";
  for (var prop in options) {
    if (options.hasOwnProperty(prop)) {
      key += options[prop];
    }
  }
  return key;
}, _animTex:function(options, chunk) {
  var vshader = "";
  vshader += options.animTexLoop ? chunk.particleAnimFrameLoopVS : chunk.particleAnimFrameClampVS;
  vshader += chunk.particleAnimTexVS;
  return vshader;
}, createShaderDefinition:function(device, options) {
  var chunk = pc.shaderChunks;
  var vshader = "";
  var fshader = pc.programlib.precisionCode(device) + "\n";
  if (options.animTex) {
    vshader += "\nuniform vec4 animTexParams;\n";
  }
  if (options.normal == 2) {
    vshader += "\nvarying mat3 ParticleMat;\n";
  }
  if (options.normal == 1) {
    vshader += "\nvarying vec3 Normal;\n";
  }
  if (options.soft) {
    vshader += "\nvarying float vDepth;\n";
  }
  if (!options.useCpu) {
    vshader += chunk.particle_initVS;
    vshader += options.pack8 ? chunk.particleInputRgba8PS : chunk.particleInputFloatPS;
    vshader += chunk.particleVS;
    if (options.localSpace) {
      vshader += chunk.particle_localShiftVS;
    }
    if (options.animTex) {
      vshader += this._animTex(options, chunk);
    }
    if (options.wrap) {
      vshader += chunk.particle_wrapVS;
    }
    if (options.alignToMotion) {
      vshader += chunk.particle_pointAlongVS;
    }
    vshader += options.mesh ? chunk.particle_meshVS : chunk.particle_billboardVS;
    if (options.normal == 1) {
      vshader += chunk.particle_normalVS;
    }
    if (options.normal == 2) {
      vshader += chunk.particle_TBNVS;
    }
    if (options.stretch > 0) {
      vshader += chunk.particle_stretchVS;
    }
    vshader += chunk.particle_endVS;
    if (options.soft > 0) {
      vshader += chunk.particle_softVS;
    }
  } else {
    vshader += chunk.particle_cpuVS;
    if (options.localSpace) {
      vshader += chunk.particle_localShiftVS;
    }
    if (options.animTex) {
      vshader += this._animTex(options, chunk);
    }
    if (options.alignToMotion) {
      vshader += chunk.particle_pointAlongVS;
    }
    vshader += options.mesh ? chunk.particle_meshVS : chunk.particle_billboardVS;
    if (options.normal == 1) {
      vshader += chunk.particle_normalVS;
    }
    if (options.normal == 2) {
      vshader += chunk.particle_TBNVS;
    }
    if (options.stretch > 0) {
      vshader += chunk.particle_stretchVS;
    }
    vshader += chunk.particle_cpu_endVS;
    if (options.soft > 0) {
      vshader += chunk.particle_softVS;
    }
  }
  vshader += "}\n";
  if (options.normal > 0) {
    if (options.normal == 1) {
      fshader += "\nvarying vec3 Normal;\n";
    } else {
      if (options.normal == 2) {
        fshader += "\nvarying mat3 ParticleMat;\n";
      }
    }
    fshader += "\nuniform vec3 lightCube[6];\n";
  }
  if (options.soft) {
    fshader += "\nvarying float vDepth;\n";
  }
  if (options.normal === 0 && options.fog === "none") {
    options.srgb = false;
  }
  fshader += pc.programlib.gammaCode(options.gamma);
  fshader += pc.programlib.tonemapCode(options.toneMap);
  if (options.fog === "linear") {
    fshader += chunk.fogLinearPS;
  } else {
    if (options.fog === "exp") {
      fshader += chunk.fogExpPS;
    } else {
      if (options.fog === "exp2") {
        fshader += chunk.fogExp2PS;
      } else {
        fshader += chunk.fogNonePS;
      }
    }
  }
  if (options.normal == 2) {
    fshader += "\nuniform sampler2D normalMap;\n";
  }
  if (options.soft > 0) {
    fshader += "\nuniform sampler2D uDepthMap;\n uniform vec4 uScreenSize;\n";
  }
  fshader += chunk.particlePS;
  if (options.soft > 0) {
    fshader += chunk.particle_softPS;
  }
  if (options.normal == 1) {
    fshader += "\nvec3 normal = Normal;\n";
  }
  if (options.normal == 2) {
    fshader += chunk.particle_normalMapPS;
  }
  if (options.normal > 0) {
    fshader += options.halflambert ? chunk.particle_halflambertPS : chunk.particle_lambertPS;
  }
  if (options.normal > 0) {
    fshader += chunk.particle_lightingPS;
  }
  if (options.blend == pc.BLEND_NORMAL) {
    fshader += chunk.particle_blendNormalPS;
  } else {
    if (options.blend == pc.BLEND_ADDITIVE) {
      fshader += chunk.particle_blendAddPS;
    } else {
      if (options.blend == pc.BLEND_MULTIPLICATIVE) {
        fshader += chunk.particle_blendMultiplyPS;
      }
    }
  }
  fshader += chunk.particle_endPS;
  var attributes = pc.shaderChunks.collectAttribs(vshader);
  return {attributes:attributes, vshader:vshader, fshader:fshader};
}};
pc.programlib.standard = {hashCode:function(str) {
  var hash = 0;
  if (str.length === 0) {
    return hash;
  }
  for (var i = 0;i < str.length;i++) {
    var char = str.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash = hash & hash;
  }
  return hash;
}, generateKey:function(device, options) {
  var props = [];
  var key = "standard";
  var light;
  for (var prop in options) {
    if (options.hasOwnProperty(prop)) {
      if (prop === "chunks") {
        for (var p in options[prop]) {
          if (options[prop].hasOwnProperty(p)) {
            props.push(p + options.chunks[p]);
          }
        }
      } else {
        if (options[prop]) {
          props.push(prop);
        }
      }
    }
  }
  props.sort();
  for (prop in props) {
    if (props.hasOwnProperty(prop)) {
      key += props[prop] + options[props[prop]];
    }
  }
  if (options.lights) {
    for (var i = 0;i < options.lights.length;i++) {
      light = options.lights[i];
      key += light.key;
    }
  }
  return this.hashCode(key);
}, _correctChannel:function(p, chan) {
  if (pc._matTex2D[p] > 0) {
    if (pc._matTex2D[p] < chan.length) {
      return chan.substring(0, pc._matTex2D[p]);
    } else {
      if (pc._matTex2D[p] > chan.length) {
        var str = chan;
        var chr = str.charAt(str.length - 1);
        var addLen = pc._matTex2D[p] - str.length;
        for (var i = 0;i < addLen;i++) {
          str += chr;
        }
        return str;
      }
    }
    return chan;
  }
}, _setMapTransform:function(codes, name, id, uv) {
  codes[0] += "uniform vec4 texture_" + name + "MapTransform;\n";
  var checkId = id + uv * 100;
  if (!codes[3][checkId]) {
    codes[1] += "varying vec2 vUV" + uv + "_" + id + ";\n";
    codes[2] += "   vUV" + uv + "_" + id + " = uv" + uv + " * texture_" + name + "MapTransform.xy + texture_" + name + "MapTransform.zw;\n";
    codes[3][checkId] = true;
  }
  return codes;
}, _uvSource:function(id, uv) {
  return id === 0 ? "vUv" + uv : "vUV" + uv + "_" + id;
}, _addMap:function(p, options, chunks, uvOffset, subCode, format) {
  var cname, tname, uname;
  var mname = p + "Map";
  var tint;
  if (options[mname + "VertexColor"]) {
    cname = mname + "Channel";
    if (!subCode) {
      tint = options[p + "Tint"];
      if (tint) {
        if (tint === 1) {
          subCode = chunks[p + "VertConstFloatPS"];
        } else {
          subCode = chunks[p + "VertConstPS"];
        }
      } else {
        subCode = chunks[p + "VertPS"];
      }
    }
    return subCode.replace(/\$CH/g, options[cname]);
  } else {
    if (options[mname]) {
      tname = mname + "Transform";
      cname = mname + "Channel";
      uname = mname + "Uv";
      var uv = this._uvSource(options[tname], options[uname]) + uvOffset;
      if (!subCode) {
        tint = options[p + "Tint"];
        if (tint) {
          if (tint === 1) {
            subCode = chunks[p + "TexConstFloatPS"];
          } else {
            subCode = chunks[p + "TexConstPS"];
          }
        } else {
          subCode = chunks[p + "TexPS"];
        }
      }
      if (format !== undefined) {
        var fmt = format === 0 ? "texture2DSRGB" : format === 1 ? "texture2DRGBM" : "texture2D";
        subCode = subCode.replace(/\$texture2DSAMPLE/g, fmt);
      }
      return subCode.replace(/\$UV/g, uv).replace(/\$CH/g, options[cname]);
    } else {
      return chunks[p + "ConstPS"];
    }
  }
}, _nonPointShadowMapProjection:function(light, shadowCoordArgs) {
  if (!light._normalOffsetBias || light._shadowType > pc.SHADOW_DEPTH) {
    if (light._type === pc.LIGHTTYPE_SPOT) {
      return "    getShadowCoordPersp" + shadowCoordArgs;
    } else {
      return "    getShadowCoordOrtho" + shadowCoordArgs;
    }
  } else {
    if (light._type == pc.LIGHTTYPE_SPOT) {
      return "    getShadowCoordPerspNormalOffset" + shadowCoordArgs;
    } else {
      return "    getShadowCoordOrthoNormalOffset" + shadowCoordArgs;
    }
  }
}, _addVaryingIfNeeded:function(code, type, name) {
  return code.indexOf(name) >= 0 ? "varying " + type + " " + name + ";\n" : "";
}, createShaderDefinition:function(device, options) {
  var i, p;
  var lighting = options.lights.length > 0;
  if (options.dirLightMap) {
    lighting = true;
    options.useSpecular = true;
  }
  if (options.shadingModel === pc.SPECULAR_PHONG) {
    options.fresnelModel = 0;
    options.specularAA = false;
    options.prefilteredCubemap = false;
    options.dpAtlas = false;
    options.ambientSH = false;
  } else {
    options.fresnelModel = options.fresnelModel === 0 ? pc.FRESNEL_SCHLICK : options.fresnelModel;
  }
  var cubemapReflection = options.cubeMap || options.prefilteredCubemap && options.useSpecular;
  var reflections = options.sphereMap || cubemapReflection || options.dpAtlas;
  var useTangents = pc.precalculatedTangents;
  var useTexCubeLod = options.useTexCubeLod;
  if (options.cubeMap || options.prefilteredCubemap) {
    options.sphereMap = null;
  }
  if (options.dpAtlas) {
    options.sphereMap = options.cubeMap = options.prefilteredCubemap = cubemapReflection = null;
  }
  if (!options.useSpecular) {
    options.specularMap = options.glossMap = null;
  }
  var needsNormal = lighting || reflections || options.ambientSH || options.prefilteredCubemap;
  this.options = options;
  var code = "";
  var codeBody = "";
  var varyings = "";
  var chunks = pc.shaderChunks;
  var lightType;
  var shadowCoordArgs;
  if (options.chunks) {
    var customChunks = [];
    for (p in chunks) {
      if (chunks.hasOwnProperty(p)) {
        if (!options.chunks[p]) {
          customChunks[p] = chunks[p];
        } else {
          customChunks[p] = options.chunks[p];
          if (!needsNormal) {
            customChunks[p] = customChunks[p].replace(/vertex_normal/g, "vec3(0)").replace(/vertex_tangent/g, "vec4(0)");
          }
        }
      }
    }
    chunks = customChunks;
  }
  if (chunks.extensionVS) {
    code += chunks.extensionVS + "\n";
  }
  code += chunks.baseVS;
  var mainShadowLight = -1;
  if (!options.noShadow) {
    for (i = 0;i < options.lights.length;i++) {
      lightType = options.lights[i]._type;
      if (options.lights[i].castShadows) {
        if (lightType === pc.LIGHTTYPE_DIRECTIONAL) {
          code += "uniform mat4 light" + i + "_shadowMatrixVS;\n";
          code += "uniform vec3 light" + i + "_shadowParamsVS;\n";
          code += "uniform vec3 light" + i + (lightType === pc.LIGHTTYPE_DIRECTIONAL ? "_directionVS" : "_positionVS") + ";\n";
          mainShadowLight = i;
          break;
        }
      }
    }
    if (mainShadowLight >= 0) {
      code += chunks.shadowCoordVS;
    }
  }
  var attributes = {vertex_position:pc.SEMANTIC_POSITION};
  codeBody += "   vPositionW    = getWorldPosition();\n";
  if (options.useInstancing) {
    attributes.instance_line1 = pc.SEMANTIC_TEXCOORD2;
    attributes.instance_line2 = pc.SEMANTIC_TEXCOORD3;
    attributes.instance_line3 = pc.SEMANTIC_TEXCOORD4;
    attributes.instance_line4 = pc.SEMANTIC_TEXCOORD5;
    code += chunks.instancingVS;
  }
  if (needsNormal) {
    attributes.vertex_normal = pc.SEMANTIC_NORMAL;
    codeBody += "   vNormalW    = dNormalW = getNormal();\n";
    if (options.sphereMap && device.fragmentUniformsCount <= 16) {
      code += chunks.viewNormalVS;
      codeBody += "   vNormalV    = getViewNormal();\n";
    }
    if ((options.heightMap || options.normalMap) && useTangents) {
      attributes.vertex_tangent = pc.SEMANTIC_TANGENT;
      code += chunks.tangentBinormalVS;
      codeBody += "   vTangentW   = getTangent();\n";
      codeBody += "   vBinormalW  = getBinormal();\n";
    }
    if (mainShadowLight >= 0) {
      lightType = options.lights[mainShadowLight]._type;
      if (lightType === pc.LIGHTTYPE_DIRECTIONAL) {
        codeBody += "   dLightDirNormW = light" + mainShadowLight + "_directionVS;\n";
      } else {
        codeBody += "   getLightDirPoint(light" + mainShadowLight + "_positionVS);\n";
      }
      shadowCoordArgs = "(light" + mainShadowLight + "_shadowMatrixVS, light" + mainShadowLight + "_shadowParamsVS);\n";
      codeBody += this._nonPointShadowMapProjection(options.lights[mainShadowLight], shadowCoordArgs);
    }
  }
  var useUv = [];
  var useUnmodifiedUv = [];
  var maxUvSets = 2;
  var cname, mname, tname, uname;
  for (p in pc._matTex2D) {
    mname = p + "Map";
    if (options[mname + "VertexColor"]) {
      cname = mname + "Channel";
      options[cname] = this._correctChannel(p, options[cname]);
    } else {
      if (options[mname]) {
        cname = mname + "Channel";
        tname = mname + "Transform";
        uname = mname + "Uv";
        options[uname] = Math.min(options[uname], maxUvSets - 1);
        options[cname] = this._correctChannel(p, options[cname]);
        var uvSet = options[uname];
        useUv[uvSet] = true;
        useUnmodifiedUv[uvSet] = useUnmodifiedUv[uvSet] || options[mname] && !options[tname];
      }
    }
  }
  if (options.forceUv1) {
    useUv[1] = true;
  }
  for (i = 0;i < maxUvSets;i++) {
    if (useUv[i]) {
      attributes["vertex_texCoord" + i] = pc["SEMANTIC_TEXCOORD" + i];
      code += chunks["uv" + i + "VS"];
      codeBody += "   vec2 uv" + i + " = getUv" + i + "();\n";
    }
    if (useUnmodifiedUv[i]) {
      codeBody += "   vUv" + i + " = uv" + i + ";\n";
    }
  }
  var codes = [code, varyings, codeBody, []];
  for (p in pc._matTex2D) {
    mname = p + "Map";
    if (options[mname]) {
      tname = mname + "Transform";
      if (options[tname]) {
        uname = mname + "Uv";
        this._setMapTransform(codes, p, options[tname], options[uname]);
      }
    }
  }
  code = codes[0];
  varyings = codes[1];
  codeBody = codes[2];
  if (options.vertexColors) {
    attributes.vertex_color = pc.SEMANTIC_COLOR;
    codeBody += "   vVertexColor = vertex_color;\n";
  }
  if (options.screenSpace) {
    code += chunks.transformScreenSpaceVS;
    if (needsNormal) {
      code += chunks.normalVS;
    }
  } else {
    if (options.skin) {
      attributes.vertex_boneWeights = pc.SEMANTIC_BLENDWEIGHT;
      attributes.vertex_boneIndices = pc.SEMANTIC_BLENDINDICES;
      code += pc.programlib.skinCode(device);
      code += chunks.transformSkinnedVS;
      if (needsNormal) {
        code += chunks.normalSkinnedVS;
      }
    } else {
      if (options.useInstancing) {
        code += chunks.transformInstancedVS;
        if (needsNormal) {
          code += chunks.normalInstancedVS;
        }
      } else {
        code += chunks.transformVS;
        if (needsNormal) {
          code += chunks.normalVS;
        }
      }
    }
  }
  code += "\n";
  code += chunks.startVS;
  code += codeBody;
  code += "}";
  var vshader = code;
  var oldVars = varyings;
  varyings = "";
  varyings += this._addVaryingIfNeeded(code, "vec4", "vMainShadowUv");
  varyings += this._addVaryingIfNeeded(code, "vec4", "vVertexColor");
  varyings += this._addVaryingIfNeeded(code, "vec3", "vPositionW");
  varyings += this._addVaryingIfNeeded(code, "vec3", "vNormalV");
  varyings += this._addVaryingIfNeeded(code, "vec3", "vNormalW");
  varyings += this._addVaryingIfNeeded(code, "vec3", "vTangentW");
  varyings += this._addVaryingIfNeeded(code, "vec3", "vBinormalW");
  varyings += this._addVaryingIfNeeded(code, "vec2", "vUv0");
  varyings += this._addVaryingIfNeeded(code, "vec2", "vUv1");
  varyings += oldVars;
  vshader = varyings + vshader;
  if (options.forceFragmentPrecision && options.forceFragmentPrecision != "highp" && options.forceFragmentPrecision !== "mediump" && options.forceFragmentPrecision !== "lowp") {
    options.forceFragmentPrecision = null;
  }
  if (options.forceFragmentPrecision) {
    if (options.forceFragmentPrecision === "highp" && device.maxPrecision !== "highp") {
      options.forceFragmentPrecision = "mediump";
    }
    if (options.forceFragmentPrecision === "mediump" && device.maxPrecision === "lowp") {
      options.forceFragmentPrecision = "lowp";
    }
  }
  var fshader;
  code = "";
  if (device.extStandardDerivatives) {
    code += "#extension GL_OES_standard_derivatives : enable\n\n";
  }
  if (chunks.extensionPS) {
    code += chunks.extensionPS + "\n";
  }
  code += options.forceFragmentPrecision ? "precision " + options.forceFragmentPrecision + " float;\n\n" : pc.programlib.precisionCode(device);
  if (options.customFragmentShader) {
    fshader = code + options.customFragmentShader;
    return {attributes:attributes, vshader:vshader, fshader:fshader, tag:pc.SHADERTAG_MATERIAL};
  }
  code += varyings;
  code += chunks.basePS;
  var codeBegin = code;
  code = "";
  var numShadowLights = 0;
  var shadowTypeUsed = [];
  var useVsm = false;
  var light;
  for (i = 0;i < options.lights.length;i++) {
    light = options.lights[i];
    lightType = light._type;
    code += "uniform vec3 light" + i + "_color;\n";
    if (lightType === pc.LIGHTTYPE_DIRECTIONAL) {
      code += "uniform vec3 light" + i + "_direction;\n";
    } else {
      code += "uniform vec3 light" + i + "_position;\n";
      code += "uniform float light" + i + "_radius;\n";
      if (lightType === pc.LIGHTTYPE_SPOT) {
        code += "uniform vec3 light" + i + "_direction;\n";
        code += "uniform float light" + i + "_innerConeAngle;\n";
        code += "uniform float light" + i + "_outerConeAngle;\n";
      }
    }
    if (light.castShadows && !options.noShadow) {
      code += "uniform mat4 light" + i + "_shadowMatrix;\n";
      if (lightType !== pc.LIGHTTYPE_DIRECTIONAL) {
        code += "uniform vec4 light" + i + "_shadowParams;\n";
      } else {
        code += "uniform vec3 light" + i + "_shadowParams;\n";
      }
      if (lightType === pc.LIGHTTYPE_POINT) {
        code += "uniform samplerCube light" + i + "_shadowMap;\n";
      } else {
        code += "uniform sampler2D light" + i + "_shadowMap;\n";
      }
      numShadowLights++;
      shadowTypeUsed[light._shadowType] = true;
      if (light._shadowType > pc.SHADOW_DEPTH) {
        useVsm = true;
      }
    }
    if (light._cookie) {
      if (light._cookie._cubemap) {
        if (lightType === pc.LIGHTTYPE_POINT) {
          code += "uniform samplerCube light" + i + "_cookie;\n";
          code += "uniform float light" + i + "_cookieIntensity;\n";
          if (!light.castShadows || options.noShadow) {
            code += "uniform mat4 light" + i + "_shadowMatrix;\n";
          }
        }
      } else {
        if (lightType === pc.LIGHTTYPE_SPOT) {
          code += "uniform sampler2D light" + i + "_cookie;\n";
          code += "uniform float light" + i + "_cookieIntensity;\n";
          if (!light.castShadows || options.noShadow) {
            code += "uniform mat4 light" + i + "_shadowMatrix;\n";
          }
          if (light._cookieTransform) {
            code += "uniform vec4 light" + i + "_cookieMatrix;\n";
            code += "uniform vec2 light" + i + "_cookieOffset;\n";
          }
        }
      }
    }
  }
  code += "\n";
  var uvOffset = options.heightMap ? " + dUvOffset" : "";
  var tbn = options.fastTbn ? chunks.TBNfastPS : chunks.TBNPS;
  if (needsNormal) {
    if (options.normalMap && useTangents) {
      code += options.packedNormal ? chunks.normalXYPS : chunks.normalXYZPS;
      var uv = this._uvSource(options.normalMapTransform, options.normalMapUv) + uvOffset;
      if (options.needsNormalFloat) {
        code += (options.fastTbn ? chunks.normalMapFloatTBNfastPS : chunks.normalMapFloatPS).replace(/\$UV/g, uv);
      } else {
        code += chunks.normalMapPS.replace(/\$UV/g, uv);
      }
      code += tbn;
    } else {
      code += chunks.normalVertexPS;
    }
  }
  code += pc.programlib.gammaCode(options.gamma);
  code += pc.programlib.tonemapCode(options.toneMap);
  code += pc.programlib.fogCode(options.fog);
  if (options.useRgbm) {
    code += chunks.rgbmPS;
  }
  if (cubemapReflection || options.prefilteredCubemap) {
    code += options.fixSeams ? chunks.fixCubemapSeamsStretchPS : chunks.fixCubemapSeamsNonePS;
  }
  if (needsNormal) {
    code += options.cubeMapProjection > 0 ? chunks.cubeMapProjectBoxPS : chunks.cubeMapProjectNonePS;
    code += options.skyboxIntensity ? chunks.envMultiplyPS : chunks.envConstPS;
  }
  code += this._addMap("diffuse", options, chunks, uvOffset);
  if (options.blendType !== pc.BLEND_NONE || options.alphaTest) {
    code += this._addMap("opacity", options, chunks, uvOffset);
  }
  code += this._addMap("emissive", options, chunks, uvOffset, null, options.emissiveFormat);
  if (options.useSpecular) {
    if (options.specularAA && options.normalMap) {
      if (options.needsNormalFloat && needsNormal) {
        code += chunks.specularAaToksvigFloatPS;
      } else {
        code += chunks.specularAaToksvigPS;
      }
    } else {
      code += chunks.specularAaNonePS;
    }
    if (options.useMetalness) {
      code += chunks.metalnessPS;
    }
    code += this._addMap(options.useMetalness ? "metalness" : "specular", options, chunks, uvOffset);
    code += this._addMap("gloss", options, chunks, uvOffset);
    if (options.fresnelModel > 0) {
      if (options.fresnelModel === pc.FRESNEL_SIMPLE) {
        code += chunks.fresnelSimplePS;
      } else {
        if (options.fresnelModel === pc.FRESNEL_SCHLICK) {
          code += chunks.fresnelSchlickPS;
        } else {
          if (options.fresnelModel === pc.FRESNEL_COMPLEX) {
            code += chunks.fresnelComplexPS;
          }
        }
      }
    }
  }
  if (options.heightMap) {
    if (!options.normalMap) {
      code += tbn;
    }
    code += this._addMap("height", options, chunks, "", chunks.parallaxPS);
  }
  var useAo = options.aoMap || options.aoMapVertexColor;
  if (useAo) {
    code += this._addMap("ao", options, chunks, uvOffset, options.aoMapVertexColor ? chunks.aoVertPS : chunks.aoTexPS);
    if (options.occludeSpecular) {
      if (options.occludeSpecular === pc.SPECOCC_AO) {
        code += options.occludeSpecularFloat ? chunks.aoSpecOccSimplePS : chunks.aoSpecOccConstSimplePS;
      } else {
        code += options.occludeSpecularFloat ? chunks.aoSpecOccPS : chunks.aoSpecOccConstPS;
      }
    }
  }
  var reflectionDecode = options.rgbmReflection ? "decodeRGBM" : options.hdrReflection ? "" : "gammaCorrectInput";
  if (cubemapReflection || options.prefilteredCubemap) {
    if (options.prefilteredCubemap) {
      if (useTexCubeLod) {
        code += chunks.reflectionPrefilteredCubeLodPS.replace(/\$DECODE/g, reflectionDecode);
      } else {
        code += chunks.reflectionPrefilteredCubePS.replace(/\$DECODE/g, reflectionDecode);
      }
    } else {
      code += chunks.reflectionCubePS.replace(/\$textureCubeSAMPLE/g, options.rgbmReflection ? "textureCubeRGBM" : options.hdrReflection ? "textureCube" : "textureCubeSRGB");
    }
  }
  if (options.sphereMap) {
    var scode = device.fragmentUniformsCount > 16 ? chunks.reflectionSpherePS : chunks.reflectionSphereLowPS;
    scode = scode.replace(/\$texture2DSAMPLE/g, options.rgbmReflection ? "texture2DRGBM" : options.hdrReflection ? "texture2D" : "texture2DSRGB");
    code += scode;
  }
  if (options.dpAtlas) {
    code += chunks.reflectionDpAtlasPS.replace(/\$texture2DSAMPLE/g, options.rgbmReflection ? "texture2DRGBM" : options.hdrReflection ? "texture2D" : "texture2DSRGB");
  }
  if ((cubemapReflection || options.sphereMap || options.dpAtlas) && options.refraction) {
    code += chunks.refractionPS;
  }
  if (numShadowLights > 0) {
    if (shadowTypeUsed[pc.SHADOW_DEPTH]) {
      code += chunks.shadowStandardPS;
    }
    if (useVsm) {
      code += chunks.shadowVSM_commonPS;
      if (shadowTypeUsed[pc.SHADOW_VSM8]) {
        code += chunks.shadowVSM8PS;
      }
      if (shadowTypeUsed[pc.SHADOW_VSM16]) {
        code += device.extTextureHalfFloatLinear ? chunks.shadowEVSMPS.replace(/\$/g, "16") : chunks.shadowEVSMnPS.replace(/\$/g, "16");
      }
      if (shadowTypeUsed[pc.SHADOW_VSM32]) {
        code += device.extTextureFloatLinear ? chunks.shadowEVSMPS.replace(/\$/g, "32") : chunks.shadowEVSMnPS.replace(/\$/g, "32");
      }
    }
    code += device.extStandardDerivatives ? chunks.biasRcvPlanePS : chunks.biasConstPS;
    code += chunks.shadowCoordPS + chunks.shadowCommonPS;
    if (mainShadowLight >= 0) {
      if (shadowTypeUsed[pc.SHADOW_DEPTH]) {
        code += chunks.shadowStandardVSPS;
      }
      if (useVsm) {
        if (shadowTypeUsed[pc.SHADOW_VSM8]) {
          code += chunks.shadowVSMVSPS.replace(/\$VSM/g, "VSM8").replace(/\$/g, "8");
        }
        if (shadowTypeUsed[pc.SHADOW_VSM16]) {
          code += chunks.shadowVSMVSPS.replace(/\$VSM/g, "VSM16").replace(/\$/g, "16");
        }
        if (shadowTypeUsed[pc.SHADOW_VSM32]) {
          code += chunks.shadowVSMVSPS.replace(/\$VSM/g, "VSM32").replace(/\$/g, "32");
        }
      }
    }
  }
  if (lighting) {
    code += chunks.lightDiffuseLambertPS;
  }
  var useOldAmbient = false;
  if (options.useSpecular) {
    code += options.shadingModel === pc.SPECULAR_PHONG ? chunks.lightSpecularPhongPS : chunks.lightSpecularBlinnPS;
    if (options.sphereMap || cubemapReflection || options.dpAtlas || options.fresnelModel > 0) {
      if (options.fresnelModel > 0) {
        if (options.conserveEnergy) {
          code += chunks.combineDiffuseSpecularPS;
        } else {
          code += chunks.combineDiffuseSpecularNoConservePS;
        }
      } else {
        code += chunks.combineDiffuseSpecularOldPS;
      }
    } else {
      if (options.diffuseMap) {
        code += chunks.combineDiffuseSpecularNoReflPS;
      } else {
        code += chunks.combineDiffuseSpecularNoReflSeparateAmbientPS;
        useOldAmbient = true;
      }
    }
  } else {
    code += chunks.combineDiffusePS;
  }
  var addAmbient = true;
  if (options.lightMap || options.lightMapVertexColor) {
    code += this._addMap("light", options, chunks, uvOffset, options.lightMapVertexColor ? chunks.lightmapSingleVertPS : options.dirLightMap ? chunks.lightmapDirPS : chunks.lightmapSinglePS, options.lightMapFormat);
    addAmbient = options.lightMapWithoutAmbient;
  }
  if (addAmbient) {
    if (options.ambientSH) {
      code += chunks.ambientSHPS;
    } else {
      if (options.prefilteredCubemap) {
        if (useTexCubeLod) {
          code += chunks.ambientPrefilteredCubeLodPS.replace(/\$DECODE/g, reflectionDecode);
        } else {
          code += chunks.ambientPrefilteredCubePS.replace(/\$DECODE/g, reflectionDecode);
        }
      } else {
        code += chunks.ambientConstantPS;
      }
    }
  }
  if (options.modulateAmbient && !useOldAmbient) {
    code += "uniform vec3 material_ambient;\n";
  }
  if (options.alphaTest) {
    code += chunks.alphaTestPS;
  }
  if (options.msdf) {
    code += chunks.msdfPS;
  }
  if (needsNormal) {
    code += chunks.viewDirPS;
    if (options.useSpecular) {
      code += chunks.reflDirPS;
    }
  }
  var hasPointLights = false;
  var usesLinearFalloff = false;
  var usesInvSquaredFalloff = false;
  var usesSpot = false;
  var usesCookie = false;
  var usesCookieNow;
  code += chunks.startPS;
  var opacityParallax = false;
  if (options.blendType === pc.BLEND_NONE && !options.alphaTest) {
    code += "   dAlpha = 1.0;\n";
  } else {
    if (options.heightMap && options.opacityMap) {
      opacityParallax = true;
    } else {
      code += "   getOpacity();\n";
      if (options.alphaTest) {
        code += "   alphaTest(dAlpha);\n";
      }
    }
  }
  if (needsNormal) {
    code += "   getViewDir();\n";
    if (options.heightMap || options.normalMap) {
      code += "   getTBN();\n";
    }
    if (options.heightMap) {
      code += "   getParallax();\n";
    }
    if (opacityParallax) {
      code += "   getOpacity();\n";
      if (options.alphaTest) {
        code += "   alphaTest(dAlpha);\n";
      }
    }
    code += "   getNormal();\n";
    if (options.useSpecular) {
      code += "   getReflDir();\n";
    }
  }
  code += "   getAlbedo();\n";
  if (lighting && options.useSpecular || reflections) {
    code += "   getSpecularity();\n";
    code += "   getGlossiness();\n";
    if (options.fresnelModel > 0) {
      code += "   getFresnel();\n";
    }
  }
  if (addAmbient) {
    code += "   addAmbient();\n";
  }
  if (options.modulateAmbient && !useOldAmbient) {
    code += "   dDiffuseLight *= material_ambient;\n";
  }
  if (useAo && !options.occludeDirect) {
    code += "    applyAO();\n";
  }
  if (options.lightMap || options.lightMapVertexColor) {
    code += "   addLightMap();\n";
  }
  if (lighting || reflections) {
    if (cubemapReflection || options.sphereMap || options.dpAtlas) {
      code += "   addReflection();\n";
    }
    if (options.dirLightMap) {
      code += "   addDirLightMap();\n";
    }
    for (i = 0;i < options.lights.length;i++) {
      light = options.lights[i];
      lightType = light._type;
      usesCookieNow = false;
      if (lightType === pc.LIGHTTYPE_DIRECTIONAL) {
        code += "   dLightDirNormW = light" + i + "_direction;\n";
        code += "   dAtten = 1.0;\n";
      } else {
        if (light._cookie) {
          if (lightType === pc.LIGHTTYPE_SPOT && !light._cookie._cubemap) {
            usesCookie = true;
            usesCookieNow = true;
          } else {
            if (lightType === pc.LIGHTTYPE_POINT && light._cookie._cubemap) {
              usesCookie = true;
              usesCookieNow = true;
            }
          }
        }
        code += "   getLightDirPoint(light" + i + "_position);\n";
        hasPointLights = true;
        if (usesCookieNow) {
          if (lightType === pc.LIGHTTYPE_SPOT) {
            code += "   dAtten3 = getCookie2D" + (light._cookieFalloff ? "" : "Clip") + (light._cookieTransform ? "Xform" : "") + "(light" + i + "_cookie, light" + i + "_shadowMatrix, light" + i + "_cookieIntensity" + (light._cookieTransform ? ", light" + i + "_cookieMatrix, light" + i + "_cookieOffset" : "") + ")." + light._cookieChannel + ";\n";
          } else {
            code += "   dAtten3 = getCookieCube(light" + i + "_cookie, light" + i + "_shadowMatrix, light" + i + "_cookieIntensity)." + light._cookieChannel + ";\n";
          }
        }
        if (light._falloffMode === pc.LIGHTFALLOFF_LINEAR) {
          code += "   dAtten = getFalloffLinear(light" + i + "_radius);\n";
          usesLinearFalloff = true;
        } else {
          code += "   dAtten = getFalloffInvSquared(light" + i + "_radius);\n";
          usesInvSquaredFalloff = true;
        }
        code += "   if (dAtten > 0.00001) {\n";
        if (lightType === pc.LIGHTTYPE_SPOT) {
          if (!(usesCookieNow && !light._cookieFalloff)) {
            code += "       dAtten *= getSpotEffect(light" + i + "_direction, light" + i + "_innerConeAngle, light" + i + "_outerConeAngle);\n";
            usesSpot = true;
          }
        }
      }
      code += "       dAtten *= getLightDiffuse();\n";
      if (light.castShadows && !options.noShadow) {
        var shadowReadMode = null;
        var evsmExp;
        if (light._shadowType === pc.SHADOW_VSM8) {
          shadowReadMode = "VSM8";
          evsmExp = "0.0";
        } else {
          if (light._shadowType === pc.SHADOW_VSM16) {
            shadowReadMode = "VSM16";
            evsmExp = "5.54";
          } else {
            if (light._shadowType === pc.SHADOW_VSM32) {
              shadowReadMode = "VSM32";
              if (device.extTextureFloatHighPrecision) {
                evsmExp = "15.0";
              } else {
                evsmExp = "5.54";
              }
            } else {
              if (options.shadowSampleType === pc.SHADOWSAMPLE_HARD) {
                shadowReadMode = "Hard";
              } else {
                if (options.shadowSampleType === pc.SHADOWSAMPLE_PCF3X3) {
                  shadowReadMode = "PCF3x3";
                }
              }
            }
          }
        }
        if (shadowReadMode !== null) {
          if (lightType === pc.LIGHTTYPE_POINT) {
            shadowCoordArgs = "(light" + i + "_shadowMap, light" + i + "_shadowParams);\n";
            if (light._normalOffsetBias) {
              code += "       normalOffsetPointShadow(light" + i + "_shadowParams);\n";
            }
            code += "       dAtten *= getShadowPoint" + shadowReadMode + shadowCoordArgs;
          } else {
            if (mainShadowLight === i) {
              shadowReadMode += "VS";
            } else {
              shadowCoordArgs = "(light" + i + "_shadowMatrix, light" + i + "_shadowParams);\n";
              code += this._nonPointShadowMapProjection(options.lights[i], shadowCoordArgs);
            }
            if (lightType === pc.LIGHTTYPE_SPOT) {
              shadowReadMode = "Spot" + shadowReadMode;
            }
            code += "       dAtten *= getShadow" + shadowReadMode + "(light" + i + "_shadowMap, light" + i + "_shadowParams" + (light._shadowType > pc.SHADOW_DEPTH ? ", " + evsmExp : "") + ");\n";
          }
        }
      }
      code += "       dDiffuseLight += dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
      if (options.useSpecular) {
        code += "       dAtten *= getLightSpecular();\n";
        code += "       dSpecularLight += dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
      }
      if (lightType !== pc.LIGHTTYPE_DIRECTIONAL) {
        code += "   }\n";
      }
      code += "\n";
    }
    if ((cubemapReflection || options.sphereMap || options.dpAtlas) && options.refraction) {
      code += "   addRefraction();\n";
    }
  }
  code += "\n";
  if (useAo) {
    if (options.occludeDirect) {
      code += "    applyAO();\n";
    }
    if (options.occludeSpecular) {
      code += "    occludeSpecular();\n";
    }
  }
  code += chunks.endPS;
  if (options.blendType === pc.BLEND_NORMAL || options.blendType === pc.BLEND_ADDITIVEALPHA) {
    code += chunks.outputAlphaPS;
  } else {
    if (options.blendType === pc.BLEND_PREMULTIPLIED) {
      code += chunks.outputAlphaPremulPS;
    } else {
      code += chunks.outputAlphaOpaquePS;
    }
  }
  if (options.msdf) {
    code += "   gl_FragColor = applyMsdf(gl_FragColor);\n";
  }
  code += "\n";
  code += pc.programlib.end();
  if (hasPointLights) {
    code = chunks.lightDirPointPS + code;
  }
  if (usesLinearFalloff) {
    code = chunks.falloffLinearPS + code;
  }
  if (usesInvSquaredFalloff) {
    code = chunks.falloffInvSquaredPS + code;
  }
  if (usesSpot) {
    code = chunks.spotPS + code;
  }
  if (usesCookie) {
    code = chunks.cookiePS + code;
  }
  var structCode = "";
  if (code.includes("dReflection")) {
    structCode += "vec4 dReflection;\n";
  }
  if (code.includes("dTBN")) {
    structCode += "mat3 dTBN;\n";
  }
  if (code.includes("dAlbedo")) {
    structCode += "vec3 dAlbedo;\n";
  }
  if (code.includes("dEmission")) {
    structCode += "vec3 dEmission;\n";
  }
  if (code.includes("dNormalW")) {
    structCode += "vec3 dNormalW;\n";
  }
  if (code.includes("dViewDirW")) {
    structCode += "vec3 dViewDirW;\n";
  }
  if (code.includes("dReflDirW")) {
    structCode += "vec3 dReflDirW;\n";
  }
  if (code.includes("dDiffuseLight")) {
    structCode += "vec3 dDiffuseLight;\n";
  }
  if (code.includes("dSpecularLight")) {
    structCode += "vec3 dSpecularLight;\n";
  }
  if (code.includes("dLightDirNormW")) {
    structCode += "vec3 dLightDirNormW;\n";
  }
  if (code.includes("dLightDirW")) {
    structCode += "vec3 dLightDirW;\n";
  }
  if (code.includes("dLightPosW")) {
    structCode += "vec3 dLightPosW;\n";
  }
  if (code.includes("dShadowCoord")) {
    structCode += "vec3 dShadowCoord;\n";
  }
  if (code.includes("dNormalMap")) {
    structCode += "vec3 dNormalMap;\n";
  }
  if (code.includes("dSpecularity")) {
    structCode += "vec3 dSpecularity;\n";
  }
  if (code.includes("dUvOffset")) {
    structCode += "vec2 dUvOffset;\n";
  }
  if (code.includes("dGlossiness")) {
    structCode += "float dGlossiness;\n";
  }
  if (code.includes("dAlpha")) {
    structCode += "float dAlpha;\n";
  }
  if (code.includes("dAtten")) {
    structCode += "float dAtten;\n";
  }
  if (code.includes("dAtten3")) {
    structCode += "vec3 dAtten3;\n";
  }
  if (code.includes("dAo")) {
    structCode += "float dAo;\n";
  }
  if (code.includes("dMsdf")) {
    structCode += "vec4 dMsdf;\n";
  }
  code = codeBegin + structCode + code;
  fshader = code;
  return {attributes:attributes, vshader:vshader, fshader:fshader, tag:pc.SHADERTAG_MATERIAL};
}};
pc.programlib.pick = {generateKey:function(device, options) {
  var key = "pick";
  if (options.skin) {
    key += "_skin";
  }
  if (options.opacityMap) {
    key += "_opam" + options.opacityChannel;
  }
  if (options.screenSpace) {
    key += "_screenspace";
  }
  return key;
}, createShaderDefinition:function(device, options) {
  var attributes = {vertex_position:pc.SEMANTIC_POSITION};
  if (options.skin) {
    attributes.vertex_boneWeights = pc.SEMANTIC_BLENDWEIGHT;
    attributes.vertex_boneIndices = pc.SEMANTIC_BLENDINDICES;
  }
  if (options.opacityMap) {
    attributes.vertex_texCoord0 = pc.SEMANTIC_TEXCOORD0;
  }
  var chunks = pc.shaderChunks;
  var code = "";
  code += chunks.transformDeclVS;
  if (options.skin) {
    code += pc.programlib.skinCode(device);
    code += chunks.transformSkinnedVS;
  } else {
    if (options.screenSpace) {
      code += chunks.transformScreenSpaceVS;
    } else {
      code += chunks.transformVS;
    }
  }
  if (options.opacityMap) {
    code += "attribute vec2 vertex_texCoord0;\n\n";
    code += "varying vec2 vUv0;\n\n";
  }
  code += pc.programlib.begin();
  code += "   gl_Position = getPosition();\n";
  if (options.opacityMap) {
    code += "    vUv0 = vertex_texCoord0;\n";
  }
  code += pc.programlib.end();
  var vshader = code;
  code = pc.programlib.precisionCode(device);
  code += "uniform vec4 uColor;";
  if (options.opacityMap) {
    code += "varying vec2 vUv0;\n\n";
    code += "uniform sampler2D texture_opacityMap;\n\n";
    code += chunks.alphaTestPS;
  }
  code += pc.programlib.begin();
  if (options.opacityMap) {
    code += "    alphaTest( texture2D(texture_opacityMap, vUv0)." + options.opacityChannel[0] + " );\n\n";
  }
  code += "    gl_FragColor = uColor;\n";
  code += pc.programlib.end();
  var fshader = code;
  return {attributes:attributes, vshader:vshader, fshader:fshader};
}};
pc.programlib.skybox = {generateKey:function(device, options) {
  var key = "skybox" + options.rgbm + " " + options.hdr + " " + options.fixSeams + "" + options.toneMapping + "" + options.gamma + "" + options.useIntensity + "" + options.mip;
  return key;
}, createShaderDefinition:function(device, options) {
  var chunks = pc.shaderChunks;
  var mip2size = [128, 64, 16, 8, 4, 2];
  return {attributes:{aPosition:pc.SEMANTIC_POSITION}, vshader:chunks.skyboxVS, fshader:pc.programlib.precisionCode(device) + (options.mip ? chunks.fixCubemapSeamsStretchPS : chunks.fixCubemapSeamsNonePS) + (options.useIntensity ? chunks.envMultiplyPS : chunks.envConstPS) + pc.programlib.gammaCode(options.gamma) + pc.programlib.tonemapCode(options.toneMapping) + chunks.rgbmPS + chunks.skyboxHDRPS.replace(/\$textureCubeSAMPLE/g, options.rgbm ? "textureCubeRGBM" : options.hdr ? "textureCube" : "textureCubeSRGB").replace(/\$FIXCONST/g, 
  1 - 1 / mip2size[options.mip] + "")};
}};
pc.extend(pc, function() {
  var primitive = {type:pc.PRIMITIVE_TRISTRIP, base:0, count:4, indexed:false};
  var PostEffect = function(graphicsDevice) {
    this.device = graphicsDevice;
    this.shader = null;
    this.depthMap = null;
    this.vertexBuffer = pc.createFullscreenQuad(graphicsDevice);
    this.needsDepthBuffer = false;
  };
  PostEffect.prototype = {render:function(inputTarget, outputTarget, rect) {
  }};
  function createFullscreenQuad(device) {
    var vertexFormat = new pc.VertexFormat(device, [{semantic:pc.SEMANTIC_POSITION, components:2, type:pc.ELEMENTTYPE_FLOAT32}]);
    var vertexBuffer = new pc.VertexBuffer(device, vertexFormat, 4);
    var iterator = new pc.VertexIterator(vertexBuffer);
    iterator.element[pc.SEMANTIC_POSITION].set(-1, -1);
    iterator.next();
    iterator.element[pc.SEMANTIC_POSITION].set(1, -1);
    iterator.next();
    iterator.element[pc.SEMANTIC_POSITION].set(-1, 1);
    iterator.next();
    iterator.element[pc.SEMANTIC_POSITION].set(1, 1);
    iterator.end();
    return vertexBuffer;
  }
  function drawFullscreenQuad(device, target, vertexBuffer, shader, rect) {
    device.setRenderTarget(target);
    device.updateBegin();
    var w = target !== null ? target.width : device.width;
    var h = target !== null ? target.height : device.height;
    var x = 0;
    var y = 0;
    if (rect) {
      x = rect.x * w;
      y = rect.y * h;
      w *= rect.z;
      h *= rect.w;
    }
    device.setViewport(x, y, w, h);
    device.setScissor(x, y, w, h);
    var oldBlending = device.getBlending();
    var oldDepthTest = device.getDepthTest();
    var oldDepthWrite = device.getDepthWrite();
    var oldCullMode = device.getCullMode();
    var oldWR = device.writeRed;
    var oldWG = device.writeGreen;
    var oldWB = device.writeBlue;
    var oldWA = device.writeAlpha;
    device.setBlending(false);
    device.setDepthTest(false);
    device.setDepthWrite(false);
    device.setCullMode(pc.CULLFACE_BACK);
    device.setColorWrite(true, true, true, true);
    device.setVertexBuffer(vertexBuffer, 0);
    device.setShader(shader);
    device.draw(primitive);
    device.setBlending(oldBlending);
    device.setDepthTest(oldDepthTest);
    device.setDepthWrite(oldDepthWrite);
    device.setCullMode(oldCullMode);
    device.setColorWrite(oldWR, oldWG, oldWB, oldWA);
    device.updateEnd();
  }
  return {PostEffect:PostEffect, createFullscreenQuad:createFullscreenQuad, drawFullscreenQuad:drawFullscreenQuad};
}());
(function() {
  var enums = {BLEND_SUBTRACTIVE:0, BLEND_ADDITIVE:1, BLEND_NORMAL:2, BLEND_NONE:3, BLEND_PREMULTIPLIED:4, BLEND_MULTIPLICATIVE:5, BLEND_ADDITIVEALPHA:6, BLEND_MULTIPLICATIVE2X:7, BLEND_SCREEN:8, FOG_NONE:"none", FOG_LINEAR:"linear", FOG_EXP:"exp", FOG_EXP2:"exp2", FRESNEL_NONE:0, FRESNEL_SCHLICK:2, LAYER_HUD:0, LAYER_GIZMO:1, LAYER_FX:2, LAYER_WORLD:15, LIGHTTYPE_DIRECTIONAL:0, LIGHTTYPE_POINT:1, LIGHTTYPE_SPOT:2, LIGHTFALLOFF_LINEAR:0, LIGHTFALLOFF_INVERSESQUARED:1, SHADOW_DEPTH:0, SHADOW_VSM8:1, 
  SHADOW_VSM16:2, SHADOW_VSM32:3, BLUR_BOX:0, BLUR_GAUSSIAN:1, SHADOWSAMPLE_HARD:0, SHADOWSAMPLE_PCF3X3:1, SHADOWSAMPLE_MASK:2, PARTICLESORT_NONE:0, PARTICLESORT_DISTANCE:1, PARTICLESORT_NEWER_FIRST:2, PARTICLESORT_OLDER_FIRST:3, PARTICLEMODE_GPU:0, PARTICLEMODE_CPU:1, EMITTERSHAPE_BOX:0, EMITTERSHAPE_SPHERE:1, PROJECTION_PERSPECTIVE:0, PROJECTION_ORTHOGRAPHIC:1, RENDERSTYLE_SOLID:0, RENDERSTYLE_WIREFRAME:1, RENDERSTYLE_POINTS:2, CUBEPROJ_NONE:0, CUBEPROJ_BOX:1, SPECULAR_PHONG:0, SPECULAR_BLINN:1, 
  GAMMA_NONE:0, GAMMA_SRGB:1, GAMMA_SRGBFAST:2, TONEMAP_LINEAR:0, TONEMAP_FILMIC:1, TONEMAP_HEJL:2, TONEMAP_ACES:3, TONEMAP_ACES2:4, SPECOCC_NONE:0, SPECOCC_AO:1, SPECOCC_GLOSSDEPENDENT:2, SHADERDEF_NOSHADOW:1, SHADERDEF_SKIN:2, SHADERDEF_UV0:4, SHADERDEF_UV1:8, SHADERDEF_VCOLOR:16, SHADERDEF_INSTANCING:32, SHADERDEF_LM:64, SHADERDEF_DIRLM:128, SHADERDEF_SCREENSPACE:256, LINEBATCH_WORLD:0, LINEBATCH_OVERLAY:1, LINEBATCH_GIZMO:2, SHADOWUPDATE_NONE:0, SHADOWUPDATE_THISFRAME:1, SHADOWUPDATE_REALTIME:2, 
  SORTKEY_FORWARD:0, SORTKEY_DEPTH:1, SHADER_FORWARD:0, SHADER_DEPTH:1, SHADER_SHADOW:2, SHADER_PICK:10, BAKE_COLOR:0, BAKE_COLORDIR:1};
  pc.extend(pc, enums);
  pc.scene = {};
  pc.extend(pc.scene, enums);
})();
pc.extend(pc, function() {
  var Scene = function Scene() {
    this.root = null;
    this._gravity = new pc.Vec3(0, -9.8, 0);
    this.drawCalls = [];
    this.shadowCasters = [];
    this.immediateDrawCalls = [];
    this._fog = pc.FOG_NONE;
    this.fogColor = new pc.Color(0, 0, 0);
    this.fogStart = 1;
    this.fogEnd = 1E3;
    this.fogDensity = 0;
    this.ambientLight = new pc.Color(0, 0, 0);
    this._gammaCorrection = pc.GAMMA_NONE;
    this._toneMapping = 0;
    this.exposure = 1;
    this._skyboxPrefiltered = [null, null, null, null, null, null];
    this._skyboxCubeMap = null;
    this._skyboxModel = null;
    this._skyboxIntensity = 1;
    this._skyboxMip = 0;
    this.lightmapSizeMultiplier = 1;
    this.lightmapMaxResolution = 2048;
    this.lightmapMode = pc.BAKE_COLORDIR;
    this._stats = {meshInstances:0, lights:0, dynamicLights:0, bakedLights:0, lastStaticPrepareFullTime:0, lastStaticPrepareSearchTime:0, lastStaticPrepareWriteTime:0, lastStaticPrepareTriAabbTime:0, lastStaticPrepareCombineTime:0};
    this._models = [];
    this._lights = [];
    this._globalLights = [];
    this._localLights = [[], []];
    this._updateShaders = true;
    this._sceneShadersVersion = 0;
    this._needsStaticPrepare = true;
  };
  Object.defineProperty(Scene.prototype, "updateShaders", {get:function() {
    return this._updateShaders;
  }, set:function(val) {
    if (val !== this._updateShaders) {
      this._updateShaders = val;
      if (!this._models) {
        return;
      }
      if (val) {
        this._sceneShadersVersion++;
      }
      for (var i = 0;i < this._models.length;i++) {
        this._models[i]._shadersVersion = this._sceneShadersVersion;
      }
    }
  }});
  Object.defineProperty(Scene.prototype, "fog", {get:function() {
    return this._fog;
  }, set:function(type) {
    if (type !== this._fog) {
      this._fog = type;
      this.updateShaders = true;
    }
  }});
  Object.defineProperty(Scene.prototype, "gammaCorrection", {get:function() {
    return this._gammaCorrection;
  }, set:function(value) {
    if (value !== this._gammaCorrection) {
      this._gammaCorrection = value;
      this.updateShaders = true;
    }
  }});
  Object.defineProperty(Scene.prototype, "toneMapping", {get:function() {
    return this._toneMapping;
  }, set:function(value) {
    if (value !== this._toneMapping) {
      this._toneMapping = value;
      this.updateShaders = true;
    }
  }});
  Object.defineProperty(Scene.prototype, "skybox", {get:function() {
    return this._skyboxCubeMap;
  }, set:function(value) {
    this._skyboxCubeMap = value;
    this._resetSkyboxModel();
    this.updateShaders = true;
  }});
  Object.defineProperty(Scene.prototype, "skyboxIntensity", {get:function() {
    return this._skyboxIntensity;
  }, set:function(value) {
    this._skyboxIntensity = value;
    this._resetSkyboxModel();
    this.updateShaders = true;
  }});
  Object.defineProperty(Scene.prototype, "skyboxMip", {get:function() {
    return this._skyboxMip;
  }, set:function(value) {
    this._skyboxMip = value;
    this._resetSkyboxModel();
    this.updateShaders = true;
  }});
  Object.defineProperty(Scene.prototype, "skyboxPrefiltered128", {get:function() {
    return this._skyboxPrefiltered[0];
  }, set:function(value) {
    if (this._skyboxPrefiltered[0] === value) {
      return;
    }
    this._skyboxPrefiltered[0] = value;
    this.updateShaders = true;
  }});
  Object.defineProperty(Scene.prototype, "skyboxPrefiltered64", {get:function() {
    return this._skyboxPrefiltered[1];
  }, set:function(value) {
    if (this._skyboxPrefiltered[1] === value) {
      return;
    }
    this._skyboxPrefiltered[1] = value;
    this.updateShaders = true;
  }});
  Object.defineProperty(Scene.prototype, "skyboxPrefiltered32", {get:function() {
    return this._skyboxPrefiltered[2];
  }, set:function(value) {
    if (this._skyboxPrefiltered[2] === value) {
      return;
    }
    this._skyboxPrefiltered[2] = value;
    this.updateShaders = true;
  }});
  Object.defineProperty(Scene.prototype, "skyboxPrefiltered16", {get:function() {
    return this._skyboxPrefiltered[3];
  }, set:function(value) {
    if (this._skyboxPrefiltered[3] === value) {
      return;
    }
    this._skyboxPrefiltered[3] = value;
    this.updateShaders = true;
  }});
  Object.defineProperty(Scene.prototype, "skyboxPrefiltered8", {get:function() {
    return this._skyboxPrefiltered[4];
  }, set:function(value) {
    if (this._skyboxPrefiltered[4] === value) {
      return;
    }
    this._skyboxPrefiltered[4] = value;
    this.updateShaders = true;
  }});
  Object.defineProperty(Scene.prototype, "skyboxPrefiltered4", {get:function() {
    return this._skyboxPrefiltered[5];
  }, set:function(value) {
    if (this._skyboxPrefiltered[5] === value) {
      return;
    }
    this._skyboxPrefiltered[5] = value;
    this.updateShaders = true;
  }});
  Scene.prototype.applySettings = function(settings) {
    this._gravity.set(settings.physics.gravity[0], settings.physics.gravity[1], settings.physics.gravity[2]);
    this.ambientLight.set(settings.render.global_ambient[0], settings.render.global_ambient[1], settings.render.global_ambient[2]);
    this._fog = settings.render.fog;
    this.fogColor.set(settings.render.fog_color[0], settings.render.fog_color[1], settings.render.fog_color[2]);
    this.fogStart = settings.render.fog_start;
    this.fogEnd = settings.render.fog_end;
    this.fogDensity = settings.render.fog_density;
    this._gammaCorrection = settings.render.gamma_correction;
    this._toneMapping = settings.render.tonemapping;
    this.lightmapSizeMultiplier = settings.render.lightmapSizeMultiplier;
    this.lightmapMaxResolution = settings.render.lightmapMaxResolution;
    this.lightmapMode = settings.render.lightmapMode;
    this.exposure = settings.render.exposure;
    this._skyboxIntensity = settings.render.skyboxIntensity === undefined ? 1 : settings.render.skyboxIntensity;
    this._skyboxMip = settings.render.skyboxMip === undefined ? 0 : settings.render.skyboxMip;
    this._resetSkyboxModel();
    this.updateShaders = true;
  };
  Scene.prototype.updateShadersFunc = function(device) {
    var i;
    if (this._skyboxCubeMap && !this._skyboxModel) {
      var material = new pc.Material;
      var scene = this;
      material.updateShader = function() {
        var library = device.getProgramLibrary();
        var shader = library.getProgram("skybox", {rgbm:scene._skyboxCubeMap.rgbm, hdr:scene._skyboxCubeMap.rgbm || scene._skyboxCubeMap.format === pc.PIXELFORMAT_RGBA32F, useIntensity:scene.skyboxIntensity !== 1, mip:scene._skyboxCubeMap.fixCubemapSeams ? scene.skyboxMip : 0, fixSeams:scene._skyboxCubeMap.fixCubemapSeams, gamma:scene.gammaCorrection, toneMapping:scene.toneMapping});
        this.setShader(shader);
      };
      material.updateShader();
      if (!this._skyboxCubeMap.fixCubemapSeams || !scene._skyboxMip) {
        material.setParameter("texture_cubeMap", this._skyboxCubeMap);
      } else {
        var mip2tex = [null, "64", "16", "8", "4"];
        var mipTex = this["skyboxPrefiltered" + mip2tex[scene._skyboxMip]];
        if (mipTex) {
          material.setParameter("texture_cubeMap", mipTex);
        }
      }
      material.cull = pc.CULLFACE_NONE;
      var node = new pc.GraphNode;
      var mesh = pc.createBox(device);
      var meshInstance = new pc.MeshInstance(node, mesh, material);
      meshInstance.updateKey = function() {
        var material = this.material;
        this.key = pc._getDrawcallSortKey(this.layer, material.blendType, false, 0);
      };
      meshInstance.updateKey();
      meshInstance.cull = false;
      meshInstance.drawToDepth = false;
      var model = new pc.Model;
      model.graph = node;
      model.meshInstances = [meshInstance];
      this._skyboxModel = model;
      this.addModel(model);
    }
    var materials = [];
    var drawCalls = this.drawCalls;
    for (i = 0;i < drawCalls.length;i++) {
      var drawCall = drawCalls[i];
      if (drawCall.material !== undefined) {
        if (materials.indexOf(drawCall.material) === -1) {
          materials.push(drawCall.material);
        }
      }
    }
    for (i = 0;i < materials.length;i++) {
      var mat = materials[i];
      if (mat.updateShader !== pc.Material.prototype.updateShader) {
        mat.clearVariants();
        mat.shader = null;
      }
    }
  };
  Scene.prototype.getModels = function() {
    return this._models;
  };
  Scene.prototype._updateStats = function() {
  };
  Scene.prototype._updateLightStats = function() {
    var stats = this._stats;
    stats.lights = this._lights.length;
    stats.dynamicLights = 0;
    stats.bakedLights = 0;
    var l;
    for (var i = 0;i < stats.lights;i++) {
      l = this._lights[i];
      if (l._enabled) {
        if (l._mask & pc.MASK_DYNAMIC || l._mask & pc.MASK_BAKED) {
          stats.dynamicLights++;
        }
        if (l._mask & pc.MASK_LIGHTMAP) {
          stats.bakedLights++;
        }
      }
    }
  };
  Scene.prototype.addModel = function(model) {
    var i, len;
    var updateModelShaders = model._shadersVersion !== this._sceneShadersVersion;
    model._shadersVersion = this._sceneShadersVersion;
    var index = this._models.indexOf(model);
    if (index === -1) {
      this._models.push(model);
      var materials = model.getMaterials();
      for (i = 0;i < materials.length;i++) {
        materials[i].scene = this;
      }
      var meshInstance;
      var numMeshInstances = model.meshInstances.length;
      for (i = 0;i < numMeshInstances;i++) {
        meshInstance = model.meshInstances[i];
        if (updateModelShaders) {
          meshInstance.material.clearVariants();
        }
        if (this.drawCalls.indexOf(meshInstance) === -1) {
          this.drawCalls.push(meshInstance);
        }
        if (meshInstance.castShadow) {
          if (this.shadowCasters.indexOf(meshInstance) === -1) {
            this.shadowCasters.push(meshInstance);
          }
        }
      }
      var lights = model.getLights();
      for (i = 0, len = lights.length;i < len;i++) {
        this.addLight(lights[i]);
      }
      this._updateStats();
    }
  };
  Scene.prototype.addShadowCaster = function(model) {
    var meshInstance;
    var numMeshInstances = model.meshInstances.length;
    for (var i = 0;i < numMeshInstances;i++) {
      meshInstance = model.meshInstances[i];
      if (meshInstance.castShadow) {
        if (this.shadowCasters.indexOf(meshInstance) === -1) {
          this.shadowCasters.push(meshInstance);
        }
      }
    }
  };
  Scene.prototype.removeModel = function(model) {
    var i, j, len, drawCall, spliceOffset, spliceCount;
    var index = this._models.indexOf(model);
    if (index !== -1) {
      this._models.splice(index, 1);
      var materials = model.getMaterials();
      for (i = 0;i < materials.length;i++) {
        materials[i].scene = null;
      }
      var meshInstance;
      var numMeshInstances = model.meshInstances.length;
      for (i = 0;i < numMeshInstances;i++) {
        meshInstance = model.meshInstances[i];
        spliceOffset = -1;
        spliceCount = 0;
        len = this.drawCalls.length;
        for (j = 0;j < len;j++) {
          drawCall = this.drawCalls[j];
          if (drawCall === meshInstance) {
            spliceOffset = j;
            spliceCount = 1;
            break;
          }
          if (drawCall._staticSource === meshInstance) {
            if (spliceOffset < 0) {
              spliceOffset = j;
            }
            spliceCount++;
          } else {
            if (spliceOffset >= 0) {
              break;
            }
          }
        }
        if (spliceOffset >= 0) {
          this.drawCalls.splice(spliceOffset, spliceCount);
        }
        if (meshInstance.castShadow) {
          index = this.shadowCasters.indexOf(meshInstance);
          if (index !== -1) {
            this.shadowCasters.splice(index, 1);
          }
        }
      }
      var lights = model.getLights();
      for (i = 0, len = lights.length;i < len;i++) {
        this.removeLight(lights[i]);
      }
      this._updateStats();
    }
  };
  Scene.prototype.removeShadowCaster = function(model) {
    var meshInstance, index;
    var numMeshInstances = model.meshInstances.length;
    for (var i = 0;i < numMeshInstances;i++) {
      meshInstance = model.meshInstances[i];
      if (meshInstance.castShadow) {
        index = this.shadowCasters.indexOf(meshInstance);
        if (index !== -1) {
          this.shadowCasters.splice(index, 1);
        }
      }
    }
  };
  Scene.prototype.containsModel = function(model) {
    return this._models.indexOf(model) >= 0;
  };
  Scene.prototype.addLight = function(light) {
    var index = this._lights.indexOf(light);
    if (index !== -1) {
      console.warn("pc.Scene#addLight: light is already in the scene");
    } else {
      this._lights.push(light);
      light._scene = this;
      this.updateShaders = true;
    }
    this._updateLightStats();
  };
  Scene.prototype.removeLight = function(light) {
    var index = this._lights.indexOf(light);
    if (index === -1) {
      console.warn("pc.Scene#removeLight: light is not in the scene");
    } else {
      this._lights.splice(index, 1);
      light._scene = null;
      this.updateShaders = true;
    }
    this._updateLightStats();
  };
  Scene.prototype._resetSkyboxModel = function() {
    if (this._skyboxModel) {
      if (this.containsModel(this._skyboxModel)) {
        this.removeModel(this._skyboxModel);
      }
    }
    this._skyboxModel = null;
  };
  Scene.prototype.setSkybox = function(cubemaps) {
    if (!cubemaps) {
      cubemaps = [null, null, null, null, null, null, null];
    }
    var different = false;
    if (this._skyboxCubeMap !== cubemaps[0]) {
      different = true;
    }
    if (!different) {
      for (var i = 0;i < 6 && !different;i++) {
        if (this._skyboxPrefiltered[i] !== cubemaps[i + 1]) {
          different = true;
        }
      }
    }
    if (!different) {
      return;
    }
    for (var i = 0;i < 6;i++) {
      this._skyboxPrefiltered[i] = cubemaps[i + 1];
    }
    this.skybox = cubemaps[0];
  };
  Scene.prototype.update = function() {
    for (var i = 0, len = this._models.length;i < len;i++) {
      this._models[i].getGraph().syncHierarchy();
    }
  };
  Scene.prototype.destroy = function() {
    var i;
    var models = this.getModels();
    for (i = 0;i < models.length;i++) {
      this.removeModel(models[i]);
    }
    for (i = 0;i < this._lights.length;i++) {
      this.removeLight(this._lights[i]);
    }
    this.skybox = null;
  };
  return {Scene:Scene};
}());
pc.extend(pc, function() {
  var scaleShift = (new pc.Mat4).mul2((new pc.Mat4).setTranslate(.5, .5, .5), (new pc.Mat4).setScale(.5, .5, .5));
  var opChanId = {r:1, g:2, b:3, a:4};
  var numShadowModes = 4;
  var directionalShadowEpsilon = .01;
  var pixelOffset = new pc.Vec2;
  var blurScissorRect = {x:1, y:1, z:0, w:0};
  var shadowCamView = new pc.Mat4;
  var shadowCamViewProj = new pc.Mat4;
  var c2sc = new pc.Mat4;
  var viewInvMat = new pc.Mat4;
  var viewMat = new pc.Mat4;
  var viewMat3 = new pc.Mat3;
  var viewProjMat = new pc.Mat4;
  var viewInvL = new pc.Mat4;
  var viewInvR = new pc.Mat4;
  var viewL = new pc.Mat4;
  var viewR = new pc.Mat4;
  var viewPosL = new pc.Vec3;
  var viewPosR = new pc.Vec3;
  var projL, projR;
  var viewMat3L = new pc.Mat4;
  var viewMat3R = new pc.Mat4;
  var viewProjMatL = new pc.Mat4;
  var viewProjMatR = new pc.Mat4;
  var frustumDiagonal = new pc.Vec3;
  var tempSphere = {center:null, radius:0};
  var meshPos;
  var visibleSceneAabb = new pc.BoundingBox;
  var lightBounds = new pc.BoundingBox;
  var culled = [];
  var filtered = [];
  var boneTextureSize = [0, 0];
  var boneTexture, instancingData, modelMatrix, normalMatrix;
  var shadowMapCache = [{}, {}, {}, {}];
  var shadowMapCubeCache = {};
  var maxBlurSize = 25;
  var keyA, keyB;
  var frustumPoints = [];
  for (var i = 0;i < 8;i++) {
    frustumPoints.push(new pc.Vec3);
  }
  function _getFrustumPoints(camera, farClip, points) {
    var nearClip = camera._nearClip;
    var fov = camera._fov * Math.PI / 180;
    var aspect = camera._aspect;
    var projection = camera._projection;
    var x, y;
    if (projection === pc.PROJECTION_PERSPECTIVE) {
      y = Math.tan(fov / 2) * nearClip;
    } else {
      y = camera._orthoHeight;
    }
    x = y * aspect;
    points[0].x = x;
    points[0].y = -y;
    points[0].z = -nearClip;
    points[1].x = x;
    points[1].y = y;
    points[1].z = -nearClip;
    points[2].x = -x;
    points[2].y = y;
    points[2].z = -nearClip;
    points[3].x = -x;
    points[3].y = -y;
    points[3].z = -nearClip;
    if (projection === pc.PROJECTION_PERSPECTIVE) {
      y = Math.tan(fov / 2) * farClip;
      x = y * aspect;
    }
    points[4].x = x;
    points[4].y = -y;
    points[4].z = -farClip;
    points[5].x = x;
    points[5].y = y;
    points[5].z = -farClip;
    points[6].x = -x;
    points[6].y = y;
    points[6].z = -farClip;
    points[7].x = -x;
    points[7].y = -y;
    points[7].z = -farClip;
    return points;
  }
  function StaticArray(size) {
    var data = new Array(size);
    var obj = function(idx) {
      return data[idx];
    };
    obj.size = 0;
    obj.push = function(v) {
      data[this.size] = v;
      ++this.size;
    };
    obj.data = data;
    return obj;
  }
  var intersectCache = {temp:[new pc.Vec3, new pc.Vec3, new pc.Vec3], vertices:new Array(3), negative:new StaticArray(3), positive:new StaticArray(3), intersections:new StaticArray(3), zCollection:new StaticArray(36)};
  function _groupVertices(coord, face, smallerIsNegative) {
    var intersections = intersectCache.intersections;
    var small, large;
    if (smallerIsNegative) {
      small = intersectCache.negative;
      large = intersectCache.positive;
    } else {
      small = intersectCache.positive;
      large = intersectCache.negative;
    }
    intersections.size = 0;
    small.size = 0;
    large.size = 0;
    var intersectCount = 0;
    var v;
    for (var j = 0;j < 3;++j) {
      v = intersectCache.vertices[j];
      if (v[coord] < face) {
        small.push(v);
      } else {
        if (v[coord] === face) {
          intersections.push(intersectCache.temp[intersections.size].copy(v));
        } else {
          large.push(v);
        }
      }
    }
  }
  function _triXFace(zs, x, y, faceTest, yMin, yMax) {
    var negative = intersectCache.negative;
    var positive = intersectCache.positive;
    var intersections = intersectCache.intersections;
    if (negative.size === 3) {
      return false;
    }
    if (negative.size && positive.size) {
      intersections.push(intersectCache.temp[intersections.size].lerp(negative(0), positive(0), (faceTest - negative(0)[x]) / (positive(0)[x] - negative(0)[x])));
      if (negative.size === 2) {
        intersections.push(intersectCache.temp[intersections.size].lerp(negative(1), positive(0), (faceTest - negative(1)[x]) / (positive(0)[x] - negative(1)[x])));
      } else {
        if (positive.size === 2) {
          intersections.push(intersectCache.temp[intersections.size].lerp(negative(0), positive(1), (faceTest - negative(0)[x]) / (positive(1)[x] - negative(0)[x])));
        }
      }
    }
    if (intersections.size === 0) {
      return true;
    }
    if (intersections.size === 1) {
      if (yMin <= intersections(0)[y] && intersections(0)[y] <= yMax) {
        zs.push(intersections(0).z);
      }
      return true;
    }
    if (intersections(1)[y] === intersections(0)[y]) {
      if (yMin <= intersections(0)[y] && intersections(0)[y] <= yMax) {
        zs.push(intersections(0).z);
        zs.push(intersections(1).z);
      }
    } else {
      var delta = (intersections(1).z - intersections(0).z) / (intersections(1)[y] - intersections(0)[y]);
      if (intersections(0)[y] > yMax) {
        zs.push(intersections(0).z + delta * (yMax - intersections(0)[y]));
      } else {
        if (intersections(0)[y] < yMin) {
          zs.push(intersections(0).z + delta * (yMin - intersections(0)[y]));
        } else {
          zs.push(intersections(0).z);
        }
      }
      if (intersections(1)[y] > yMax) {
        zs.push(intersections(1).z + delta * (yMax - intersections(1)[y]));
      } else {
        if (intersections(1)[y] < yMin) {
          zs.push(intersections(1).z + delta * (yMin - intersections(1)[y]));
        } else {
          zs.push(intersections(1).z);
        }
      }
    }
    return true;
  }
  var _sceneAABB_LS = [new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3];
  var iAABBTriIndexes = [0, 1, 2, 1, 2, 3, 4, 5, 6, 5, 6, 7, 0, 2, 4, 2, 4, 6, 1, 3, 5, 3, 5, 7, 0, 1, 4, 1, 4, 5, 2, 3, 6, 3, 6, 7];
  function _getZFromAABB(w2sc, aabbMin, aabbMax, lcamMinX, lcamMaxX, lcamMinY, lcamMaxY) {
    _sceneAABB_LS[0].x = _sceneAABB_LS[1].x = _sceneAABB_LS[2].x = _sceneAABB_LS[3].x = aabbMin.x;
    _sceneAABB_LS[1].y = _sceneAABB_LS[3].y = _sceneAABB_LS[7].y = _sceneAABB_LS[5].y = aabbMin.y;
    _sceneAABB_LS[2].z = _sceneAABB_LS[3].z = _sceneAABB_LS[6].z = _sceneAABB_LS[7].z = aabbMin.z;
    _sceneAABB_LS[4].x = _sceneAABB_LS[5].x = _sceneAABB_LS[6].x = _sceneAABB_LS[7].x = aabbMax.x;
    _sceneAABB_LS[0].y = _sceneAABB_LS[2].y = _sceneAABB_LS[4].y = _sceneAABB_LS[6].y = aabbMax.y;
    _sceneAABB_LS[0].z = _sceneAABB_LS[1].z = _sceneAABB_LS[4].z = _sceneAABB_LS[5].z = aabbMax.z;
    for (var i = 0;i < 8;++i) {
      w2sc.transformPoint(_sceneAABB_LS[i], _sceneAABB_LS[i]);
    }
    var minz = 9999999999;
    var maxz = -9999999999;
    var vertices = intersectCache.vertices;
    var positive = intersectCache.positive;
    var zs = intersectCache.zCollection;
    zs.size = 0;
    for (var AABBTriIter = 0;AABBTriIter < 12;++AABBTriIter) {
      vertices[0] = _sceneAABB_LS[iAABBTriIndexes[AABBTriIter * 3 + 0]];
      vertices[1] = _sceneAABB_LS[iAABBTriIndexes[AABBTriIter * 3 + 1]];
      vertices[2] = _sceneAABB_LS[iAABBTriIndexes[AABBTriIter * 3 + 2]];
      var verticesWithinBound = 0;
      _groupVertices("x", lcamMinX, true);
      if (!_triXFace(zs, "x", "y", lcamMinX, lcamMinY, lcamMaxY)) {
        continue;
      }
      verticesWithinBound += positive.size;
      _groupVertices("x", lcamMaxX, false);
      if (!_triXFace(zs, "x", "y", lcamMaxX, lcamMinY, lcamMaxY)) {
        continue;
      }
      verticesWithinBound += positive.size;
      _groupVertices("y", lcamMinY, true);
      if (!_triXFace(zs, "y", "x", lcamMinY, lcamMinX, lcamMaxX)) {
        continue;
      }
      verticesWithinBound += positive.size;
      _groupVertices("y", lcamMaxY, false);
      _triXFace(zs, "y", "x", lcamMaxY, lcamMinX, lcamMaxX);
      if (verticesWithinBound + positive.size == 12) {
        zs.push(vertices[0].z);
        zs.push(vertices[1].z);
        zs.push(vertices[2].z);
      }
    }
    var z;
    for (var j = 0, len = zs.size;j < len;j++) {
      z = zs(j);
      if (z < minz) {
        minz = z;
      }
      if (z > maxz) {
        maxz = z;
      }
    }
    return {min:minz, max:maxz};
  }
  function _getZFromAABBSimple(w2sc, aabbMin, aabbMax, lcamMinX, lcamMaxX, lcamMinY, lcamMaxY) {
    _sceneAABB_LS[0].x = _sceneAABB_LS[1].x = _sceneAABB_LS[2].x = _sceneAABB_LS[3].x = aabbMin.x;
    _sceneAABB_LS[1].y = _sceneAABB_LS[3].y = _sceneAABB_LS[7].y = _sceneAABB_LS[5].y = aabbMin.y;
    _sceneAABB_LS[2].z = _sceneAABB_LS[3].z = _sceneAABB_LS[6].z = _sceneAABB_LS[7].z = aabbMin.z;
    _sceneAABB_LS[4].x = _sceneAABB_LS[5].x = _sceneAABB_LS[6].x = _sceneAABB_LS[7].x = aabbMax.x;
    _sceneAABB_LS[0].y = _sceneAABB_LS[2].y = _sceneAABB_LS[4].y = _sceneAABB_LS[6].y = aabbMax.y;
    _sceneAABB_LS[0].z = _sceneAABB_LS[1].z = _sceneAABB_LS[4].z = _sceneAABB_LS[5].z = aabbMax.z;
    var minz = 9999999999;
    var maxz = -9999999999;
    var z;
    for (var i = 0;i < 8;++i) {
      w2sc.transformPoint(_sceneAABB_LS[i], _sceneAABB_LS[i]);
      z = _sceneAABB_LS[i].z;
      if (z < minz) {
        minz = z;
      }
      if (z > maxz) {
        maxz = z;
      }
    }
    return {min:minz, max:maxz};
  }
  function getShadowFormat(shadowType) {
    if (shadowType === pc.SHADOW_VSM32) {
      return pc.PIXELFORMAT_RGBA32F;
    } else {
      if (shadowType === pc.SHADOW_VSM16) {
        return pc.PIXELFORMAT_RGBA16F;
      }
    }
    return pc.PIXELFORMAT_R8_G8_B8_A8;
  }
  function getShadowFiltering(device, shadowType) {
    if (shadowType === pc.SHADOW_DEPTH) {
      return pc.FILTER_NEAREST;
    } else {
      if (shadowType === pc.SHADOW_VSM32) {
        return device.extTextureFloatLinear ? pc.FILTER_LINEAR : pc.FILTER_NEAREST;
      } else {
        if (shadowType === pc.SHADOW_VSM16) {
          return device.extTextureHalfFloatLinear ? pc.FILTER_LINEAR : pc.FILTER_NEAREST;
        }
      }
    }
    return pc.FILTER_LINEAR;
  }
  function createShadowMap(device, width, height, shadowType) {
    var format = getShadowFormat(shadowType);
    var filter = getShadowFiltering(device, shadowType);
    var shadowMap = new pc.Texture(device, {format:format, width:width, height:height, mipmaps:false, minFilter:filter, magFilter:filter, addressU:pc.ADDRESS_CLAMP_TO_EDGE, addressV:pc.ADDRESS_CLAMP_TO_EDGE});
    return new pc.RenderTarget(device, shadowMap, true);
  }
  function createShadowCubeMap(device, size) {
    var cubemap = new pc.Texture(device, {format:pc.PIXELFORMAT_R8_G8_B8_A8, width:size, height:size, cubemap:true, mipmaps:false, minFilter:pc.FILTER_NEAREST, magFilter:pc.FILTER_NEAREST, addressU:pc.ADDRESS_CLAMP_TO_EDGE, addressV:pc.ADDRESS_CLAMP_TO_EDGE});
    var targets = [];
    for (var i = 0;i < 6;i++) {
      var target = new pc.RenderTarget(device, cubemap, {face:i, depth:true});
      targets.push(target);
    }
    return targets;
  }
  function gauss(x, sigma) {
    return Math.exp(-(x * x) / (2 * sigma * sigma));
  }
  function gaussWeights(kernelSize) {
    if (kernelSize > maxBlurSize) {
      kernelSize = maxBlurSize;
    }
    var sigma = (kernelSize - 1) / (2 * 3);
    var i, values, sum, halfWidth;
    halfWidth = (kernelSize - 1) * .5;
    values = new Array(kernelSize);
    sum = 0;
    for (i = 0;i < kernelSize;++i) {
      values[i] = gauss(i - halfWidth, sigma);
      sum += values[i];
    }
    for (i = 0;i < kernelSize;++i) {
      values[i] /= sum;
    }
    return values;
  }
  function createShadowCamera(device, shadowType) {
    var flags = pc.CLEARFLAG_DEPTH;
    if (!device.extDepthTexture) {
      flags |= pc.CLEARFLAG_COLOR;
    }
    var shadowCam = new pc.Camera;
    if (shadowType > pc.SHADOW_DEPTH) {
      shadowCam.clearColor[0] = 0;
      shadowCam.clearColor[1] = 0;
      shadowCam.clearColor[2] = 0;
      shadowCam.clearColor[3] = 0;
    } else {
      shadowCam.clearColor[0] = 1;
      shadowCam.clearColor[1] = 1;
      shadowCam.clearColor[2] = 1;
      shadowCam.clearColor[3] = 1;
    }
    shadowCam.clearDepth = 1;
    shadowCam.clearFlags = flags;
    shadowCam.clearStencil = null;
    shadowCam._node = new pc.GraphNode;
    return shadowCam;
  }
  function getShadowMapFromCache(device, res, mode, layer) {
    if (!layer) {
      layer = 0;
    }
    var id = layer * 1E4 + res;
    var shadowBuffer = shadowMapCache[mode][id];
    if (!shadowBuffer) {
      shadowBuffer = createShadowMap(device, res, res, mode ? mode : pc.SHADOW_DEPTH);
      shadowMapCache[mode][id] = shadowBuffer;
    }
    return shadowBuffer;
  }
  function createShadowBuffer(device, light) {
    var shadowBuffer;
    if (light._type === pc.LIGHTTYPE_POINT) {
      if (light._shadowType > pc.SHADOW_DEPTH) {
        light._shadowType = pc.SHADOW_DEPTH;
      }
      if (light._cacheShadowMap) {
        shadowBuffer = shadowMapCubeCache[light._shadowResolution];
        if (!shadowBuffer) {
          shadowBuffer = createShadowCubeMap(device, light._shadowResolution);
          shadowMapCubeCache[light._shadowResolution] = shadowBuffer;
        }
      } else {
        shadowBuffer = createShadowCubeMap(device, light._shadowResolution);
      }
      light._shadowCamera.renderTarget = shadowBuffer[0];
      light._shadowCubeMap = shadowBuffer;
    } else {
      if (light._cacheShadowMap) {
        shadowBuffer = getShadowMapFromCache(device, light._shadowResolution, light._shadowType);
      } else {
        shadowBuffer = createShadowMap(device, light._shadowResolution, light._shadowResolution, light._shadowType);
      }
      light._shadowCamera.renderTarget = shadowBuffer;
    }
  }
  function getDepthKey(meshInstance) {
    var material = meshInstance.material;
    var x = meshInstance.skinInstance ? 10 : 0;
    var y = 0;
    if (material.opacityMap) {
      var opChan = material.opacityMapChannel;
      if (opChan) {
        y = opChanId[opChan];
      }
    }
    return x + y;
  }
  function ForwardRenderer(graphicsDevice) {
    this.device = graphicsDevice;
    var device = this.device;
    this._depthDrawCalls = 0;
    this._shadowDrawCalls = 0;
    this._forwardDrawCalls = 0;
    this._skinDrawCalls = 0;
    this._instancedDrawCalls = 0;
    this._immediateRendered = 0;
    this._removedByInstancing = 0;
    this._camerasRendered = 0;
    this._materialSwitches = 0;
    this._shadowMapUpdates = 0;
    this._shadowMapTime = 0;
    this._depthMapTime = 0;
    this._forwardTime = 0;
    this._cullTime = 0;
    this._sortTime = 0;
    this._skinTime = 0;
    this._instancingTime = 0;
    var library = device.getProgramLibrary();
    this.library = library;
    this.frontToBack = false;
    this._depthShaderStatic = library.getProgram("depth", {skin:false});
    this._depthShaderSkin = library.getProgram("depth", {skin:true});
    this._depthShaderStaticOp = {};
    this._depthShaderSkinOp = {};
    var chan = ["r", "g", "b", "a"];
    for (var c = 0;c < 4;c++) {
      this._depthShaderStaticOp[chan[c]] = library.getProgram("depth", {skin:false, opacityMap:true, opacityChannel:chan[c]});
      this._depthShaderSkinOp[chan[c]] = library.getProgram("depth", {skin:true, opacityMap:true, opacityChannel:chan[c]});
      this._depthShaderStaticOp[chan[c]] = library.getProgram("depth", {skin:false, opacityMap:true, opacityChannel:chan[c]});
      this._depthShaderSkinOp[chan[c]] = library.getProgram("depth", {skin:true, opacityMap:true, opacityChannel:chan[c]});
    }
    var scope = device.scope;
    this.projId = scope.resolve("matrix_projection");
    this.viewId = scope.resolve("matrix_view");
    this.viewId3 = scope.resolve("matrix_view3");
    this.viewInvId = scope.resolve("matrix_viewInverse");
    this.viewProjId = scope.resolve("matrix_viewProjection");
    this.viewPosId = scope.resolve("view_position");
    this.nearClipId = scope.resolve("camera_near");
    this.farClipId = scope.resolve("camera_far");
    this.shadowMapLightRadiusId = scope.resolve("light_radius");
    this.fogColorId = scope.resolve("fog_color");
    this.fogStartId = scope.resolve("fog_start");
    this.fogEndId = scope.resolve("fog_end");
    this.fogDensityId = scope.resolve("fog_density");
    this.modelMatrixId = scope.resolve("matrix_model");
    this.normalMatrixId = scope.resolve("matrix_normal");
    this.poseMatrixId = scope.resolve("matrix_pose[0]");
    this.boneTextureId = scope.resolve("texture_poseMap");
    this.boneTextureSizeId = scope.resolve("texture_poseMapSize");
    this.skinPosOffsetId = scope.resolve("skinPosOffset");
    this.alphaTestId = scope.resolve("alpha_ref");
    this.opacityMapId = scope.resolve("texture_opacityMap");
    this.ambientId = scope.resolve("light_globalAmbient");
    this.exposureId = scope.resolve("exposure");
    this.skyboxIntensityId = scope.resolve("skyboxIntensity");
    this.lightColorId = [];
    this.lightDirId = [];
    this.lightShadowMapId = [];
    this.lightShadowMatrixId = [];
    this.lightShadowParamsId = [];
    this.lightShadowMatrixVsId = [];
    this.lightShadowParamsVsId = [];
    this.lightDirVsId = [];
    this.lightRadiusId = [];
    this.lightPosId = [];
    this.lightInAngleId = [];
    this.lightOutAngleId = [];
    this.lightPosVsId = [];
    this.lightCookieId = [];
    this.lightCookieIntId = [];
    this.lightCookieMatrixId = [];
    this.lightCookieOffsetId = [];
    this.depthMapId = scope.resolve("uDepthMap");
    this.screenSizeId = scope.resolve("uScreenSize");
    this._screenSize = new pc.Vec4;
    this.sourceId = scope.resolve("source");
    this.pixelOffsetId = scope.resolve("pixelOffset");
    this.weightId = scope.resolve("weight[0]");
    var chunks = pc.shaderChunks;
    this.blurVsmShaderCode = [chunks.blurVSMPS, "#define GAUSS\n" + chunks.blurVSMPS];
    var packed = "#define PACKED\n";
    this.blurPackedVsmShaderCode = [packed + this.blurVsmShaderCode[0], packed + this.blurVsmShaderCode[1]];
    this.blurVsmShader = [{}, {}];
    this.blurPackedVsmShader = [{}, {}];
    this.blurVsmWeights = {};
    this.fogColor = new Float32Array(3);
    this.ambientColor = new Float32Array(3);
  }
  function mat3FromMat4(m3, m4) {
    m3.data[0] = m4.data[0];
    m3.data[1] = m4.data[1];
    m3.data[2] = m4.data[2];
    m3.data[3] = m4.data[4];
    m3.data[4] = m4.data[5];
    m3.data[5] = m4.data[6];
    m3.data[6] = m4.data[8];
    m3.data[7] = m4.data[9];
    m3.data[8] = m4.data[10];
  }
  pc.extend(ForwardRenderer.prototype, {sortCompare:function(drawCallA, drawCallB) {
    if (drawCallA.layer === drawCallB.layer) {
      if (drawCallA.drawOrder && drawCallB.drawOrder) {
        return drawCallA.drawOrder - drawCallB.drawOrder;
      } else {
        if (drawCallA.zdist && drawCallB.zdist) {
          return drawCallB.zdist - drawCallA.zdist;
        } else {
          if (drawCallA.zdist2 && drawCallB.zdist2) {
            return drawCallA.zdist2 - drawCallB.zdist2;
          }
        }
      }
    }
    return drawCallB._key[pc.SORTKEY_FORWARD] - drawCallA._key[pc.SORTKEY_FORWARD];
  }, sortCompareMesh:function(drawCallA, drawCallB) {
    if (drawCallA.layer === drawCallB.layer) {
      if (drawCallA.drawOrder && drawCallB.drawOrder) {
        return drawCallA.drawOrder - drawCallB.drawOrder;
      } else {
        if (drawCallA.zdist && drawCallB.zdist) {
          return drawCallB.zdist - drawCallA.zdist;
        }
      }
    }
    keyA = drawCallA._key[pc.SORTKEY_FORWARD];
    keyB = drawCallB._key[pc.SORTKEY_FORWARD];
    if (keyA === keyB && drawCallA.mesh && drawCallB.mesh) {
      return drawCallB.mesh.id - drawCallA.mesh.id;
    }
    return keyB - keyA;
  }, depthSortCompare:function(drawCallA, drawCallB) {
    keyA = drawCallA._key[pc.SORTKEY_DEPTH];
    keyB = drawCallB._key[pc.SORTKEY_DEPTH];
    if (keyA === keyB && drawCallA.mesh && drawCallB.mesh) {
      return drawCallB.mesh.id - drawCallA.mesh.id;
    }
    return keyB - keyA;
  }, lightCompare:function(lightA, lightB) {
    return lightA.key - lightB.key;
  }, _isVisible:function(camera, meshInstance) {
    if (!meshInstance.visible) {
      return false;
    }
    meshPos = meshInstance.aabb.center;
    if (meshInstance._aabb._radiusVer !== meshInstance._aabbVer) {
      meshInstance._aabb._radius = meshInstance._aabb.halfExtents.length();
      meshInstance._aabb._radiusVer = meshInstance._aabbVer;
    }
    tempSphere.radius = meshInstance._aabb._radius;
    tempSphere.center = meshPos;
    return camera.frustum.containsSphere(tempSphere);
  }, getShadowCamera:function(device, light) {
    var shadowCam = light._shadowCamera;
    var shadowBuffer;
    if (shadowCam === null) {
      shadowCam = light._shadowCamera = createShadowCamera(device, light._shadowType);
      createShadowBuffer(device, light);
    } else {
      shadowBuffer = shadowCam.renderTarget;
      if (shadowBuffer.width !== light._shadowResolution || shadowBuffer.height !== light._shadowResolution) {
        createShadowBuffer(device, light);
      }
    }
    return shadowCam;
  }, updateCameraFrustum:function(camera) {
    var projMat;
    if (camera.vrDisplay && camera.vrDisplay.presenting) {
      projMat = camera.vrDisplay.combinedProj;
      var parent = camera._node.getParent();
      if (parent) {
        viewMat.copy(parent.getWorldTransform()).mul(camera.vrDisplay.combinedViewInv).invert();
      } else {
        viewMat.copy(camera.vrDisplay.combinedView);
      }
      viewInvMat.copy(viewMat).invert();
      this.viewInvId.setValue(viewInvMat.data);
      camera.frustum.update(projMat, viewMat);
      return;
    }
    projMat = camera.getProjectionMatrix();
    var pos = camera._node.getPosition();
    var rot = camera._node.getRotation();
    viewInvMat.setTRS(pos, rot, pc.Vec3.ONE);
    this.viewInvId.setValue(viewInvMat.data);
    viewMat.copy(viewInvMat).invert();
    camera.frustum.update(projMat, viewMat);
  }, setCamera:function(camera, cullBorder) {
    var vrDisplay = camera.vrDisplay;
    if (!vrDisplay || !vrDisplay.presenting) {
      var projMat = camera.getProjectionMatrix();
      this.projId.setValue(projMat.data);
      var pos = camera._node.getPosition();
      var rot = camera._node.getRotation();
      viewInvMat.setTRS(pos, rot, pc.Vec3.ONE);
      this.viewInvId.setValue(viewInvMat.data);
      viewMat.copy(viewInvMat).invert();
      this.viewId.setValue(viewMat.data);
      mat3FromMat4(viewMat3, viewMat);
      this.viewId3.setValue(viewMat3.data);
      viewProjMat.mul2(projMat, viewMat);
      this.viewProjId.setValue(viewProjMat.data);
      this.viewPosId.setValue(camera._node.getPosition().data);
      camera.frustum.update(projMat, viewMat);
    } else {
      projL = vrDisplay.leftProj;
      projR = vrDisplay.rightProj;
      var parent = camera._node.getParent();
      if (parent) {
        var transform = parent.getWorldTransform();
        viewInvL.mul2(transform, vrDisplay.leftViewInv);
        viewInvR.mul2(transform, vrDisplay.rightViewInv);
        viewL.copy(viewInvL).invert();
        viewR.copy(viewInvR).invert();
        viewMat.copy(parent.getWorldTransform()).mul(vrDisplay.combinedViewInv).invert();
      } else {
        viewInvL.copy(vrDisplay.leftViewInv);
        viewInvR.copy(vrDisplay.rightViewInv);
        viewL.copy(vrDisplay.leftView);
        viewR.copy(vrDisplay.rightView);
        viewMat.copy(vrDisplay.combinedView);
      }
      mat3FromMat4(viewMat3L, viewL);
      mat3FromMat4(viewMat3R, viewR);
      viewProjMatL.mul2(vrDisplay.leftProj, viewL);
      viewProjMatR.mul2(vrDisplay.rightProj, viewR);
      viewPosL.data[0] = viewInvL.data[12];
      viewPosL.data[1] = viewInvL.data[13];
      viewPosL.data[2] = viewInvL.data[14];
      viewPosR.data[0] = viewInvR.data[12];
      viewPosR.data[1] = viewInvR.data[13];
      viewPosR.data[2] = viewInvR.data[14];
      camera.frustum.update(vrDisplay.combinedProj, viewMat);
    }
    this.nearClipId.setValue(camera._nearClip);
    this.farClipId.setValue(camera._farClip);
    var device = this.device;
    var target = camera.renderTarget;
    device.setRenderTarget(target);
    device.updateBegin();
    var rect = camera.getRect();
    var pixelWidth = target ? target.width : device.width;
    var pixelHeight = target ? target.height : device.height;
    var x = Math.floor(rect.x * pixelWidth);
    var y = Math.floor(rect.y * pixelHeight);
    var w = Math.floor(rect.width * pixelWidth);
    var h = Math.floor(rect.height * pixelHeight);
    device.setViewport(x, y, w, h);
    device.setScissor(x, y, w, h);
    device.clear(camera._clearOptions);
    if (cullBorder) {
      device.setScissor(1, 1, pixelWidth - 2, pixelHeight - 2);
    }
  }, dispatchGlobalLights:function(scene) {
    var i;
    this.mainLight = -1;
    var scope = this.device.scope;
    this.ambientColor[0] = scene.ambientLight.data[0];
    this.ambientColor[1] = scene.ambientLight.data[1];
    this.ambientColor[2] = scene.ambientLight.data[2];
    if (scene.gammaCorrection) {
      for (i = 0;i < 3;i++) {
        this.ambientColor[i] = Math.pow(this.ambientColor[i], 2.2);
      }
    }
    this.ambientId.setValue(this.ambientColor);
    this.exposureId.setValue(scene.exposure);
    if (scene._skyboxModel) {
      this.skyboxIntensityId.setValue(scene.skyboxIntensity);
    }
  }, _resolveLight:function(scope, i) {
    var light = "light" + i;
    this.lightColorId[i] = scope.resolve(light + "_color");
    this.lightDirId[i] = scope.resolve(light + "_direction");
    this.lightShadowMapId[i] = scope.resolve(light + "_shadowMap");
    this.lightShadowMatrixId[i] = scope.resolve(light + "_shadowMatrix");
    this.lightShadowParamsId[i] = scope.resolve(light + "_shadowParams");
    this.lightShadowMatrixVsId[i] = scope.resolve(light + "_shadowMatrixVS");
    this.lightShadowParamsVsId[i] = scope.resolve(light + "_shadowParamsVS");
    this.lightDirVsId[i] = scope.resolve(light + "_directionVS");
    this.lightRadiusId[i] = scope.resolve(light + "_radius");
    this.lightPosId[i] = scope.resolve(light + "_position");
    this.lightInAngleId[i] = scope.resolve(light + "_innerConeAngle");
    this.lightOutAngleId[i] = scope.resolve(light + "_outerConeAngle");
    this.lightPosVsId[i] = scope.resolve(light + "_positionVS");
    this.lightCookieId[i] = scope.resolve(light + "_cookie");
    this.lightCookieIntId[i] = scope.resolve(light + "_cookieIntensity");
    this.lightCookieMatrixId[i] = scope.resolve(light + "_cookieMatrix");
    this.lightCookieOffsetId[i] = scope.resolve(light + "_cookieOffset");
  }, dispatchDirectLights:function(scene, mask) {
    var dirs = scene._globalLights;
    var numDirs = dirs.length;
    var i;
    var directional, wtm;
    var cnt = 0;
    var scope = this.device.scope;
    for (i = 0;i < numDirs;i++) {
      if (!(dirs[i]._mask & mask)) {
        continue;
      }
      directional = dirs[i];
      wtm = directional._node.getWorldTransform();
      if (!this.lightColorId[cnt]) {
        this._resolveLight(scope, cnt);
      }
      this.lightColorId[cnt].setValue(scene.gammaCorrection ? directional._linearFinalColor.data : directional._finalColor.data);
      wtm.getY(directional._direction).scale(-1);
      this.lightDirId[cnt].setValue(directional._direction.normalize().data);
      if (directional.castShadows) {
        var shadowMap = this.device.extDepthTexture ? directional._shadowCamera.renderTarget._depthTexture : directional._shadowCamera.renderTarget.colorBuffer;
        var bias;
        if (directional._shadowType > pc.SHADOW_DEPTH) {
          bias = -1E-5 * 20;
        } else {
          bias = directional.shadowBias / directional._shadowCamera._farClip * 100;
          if (this.device.extStandardDerivatives) {
            bias *= -100;
          }
        }
        var normalBias = directional._shadowType > pc.SHADOW_DEPTH ? directional.vsmBias / (directional._shadowCamera._farClip / 7) : directional._normalOffsetBias;
        this.lightShadowMapId[cnt].setValue(shadowMap);
        this.lightShadowMatrixId[cnt].setValue(directional._shadowMatrix.data);
        var params = directional._rendererParams;
        if (params.length !== 3) {
          params.length = 3;
        }
        params[0] = directional._shadowResolution;
        params[1] = normalBias;
        params[2] = bias;
        this.lightShadowParamsId[cnt].setValue(params);
        if (this.mainLight < 0) {
          this.lightShadowMatrixVsId[cnt].setValue(directional._shadowMatrix.data);
          this.lightShadowParamsVsId[cnt].setValue(params);
          this.lightDirVsId[cnt].setValue(directional._direction.normalize().data);
          this.mainLight = i;
        }
      }
      cnt++;
    }
    return cnt;
  }, dispatchPointLight:function(scene, scope, point, cnt) {
    var wtm = point._node.getWorldTransform();
    if (!this.lightColorId[cnt]) {
      this._resolveLight(scope, cnt);
    }
    this.lightRadiusId[cnt].setValue(point.attenuationEnd);
    this.lightColorId[cnt].setValue(scene.gammaCorrection ? point._linearFinalColor.data : point._finalColor.data);
    wtm.getTranslation(point._position);
    this.lightPosId[cnt].setValue(point._position.data);
    if (point.castShadows) {
      var shadowMap = this.device.extDepthTexture ? point._shadowCamera.renderTarget._depthTexture : point._shadowCamera.renderTarget.colorBuffer;
      this.lightShadowMapId[cnt].setValue(shadowMap);
      var params = point._rendererParams;
      if (params.length !== 4) {
        params.length = 4;
      }
      params[0] = point._shadowResolution;
      params[1] = point._normalOffsetBias;
      params[2] = point.shadowBias;
      params[3] = 1 / point.attenuationEnd;
      this.lightShadowParamsId[cnt].setValue(params);
    }
    if (point._cookie) {
      this.lightCookieId[cnt].setValue(point._cookie);
      this.lightShadowMatrixId[cnt].setValue(wtm.data);
      this.lightCookieIntId[cnt].setValue(point.cookieIntensity);
    }
  }, dispatchSpotLight:function(scene, scope, spot, cnt) {
    var wtm = spot._node.getWorldTransform();
    if (!this.lightColorId[cnt]) {
      this._resolveLight(scope, cnt);
    }
    this.lightInAngleId[cnt].setValue(spot._innerConeAngleCos);
    this.lightOutAngleId[cnt].setValue(spot._outerConeAngleCos);
    this.lightRadiusId[cnt].setValue(spot.attenuationEnd);
    this.lightColorId[cnt].setValue(scene.gammaCorrection ? spot._linearFinalColor.data : spot._finalColor.data);
    wtm.getTranslation(spot._position);
    this.lightPosId[cnt].setValue(spot._position.data);
    wtm.getY(spot._direction).scale(-1);
    this.lightDirId[cnt].setValue(spot._direction.normalize().data);
    if (spot.castShadows) {
      var bias;
      if (spot._shadowType > pc.SHADOW_DEPTH) {
        bias = -1E-5 * 20;
      } else {
        bias = spot.shadowBias * 20;
        if (this.device.extStandardDerivatives) {
          bias *= -100;
        }
      }
      var normalBias = spot._shadowType > pc.SHADOW_DEPTH ? spot.vsmBias / (spot.attenuationEnd / 7) : spot._normalOffsetBias;
      var shadowMap = this.device.extDepthTexture ? spot._shadowCamera.renderTarget._depthTexture : spot._shadowCamera.renderTarget.colorBuffer;
      this.lightShadowMapId[cnt].setValue(shadowMap);
      this.lightShadowMatrixId[cnt].setValue(spot._shadowMatrix.data);
      var params = spot._rendererParams;
      if (params.length !== 4) {
        params.length = 4;
      }
      params[0] = spot._shadowResolution;
      params[1] = normalBias;
      params[2] = bias;
      params[3] = 1 / spot.attenuationEnd;
      this.lightShadowParamsId[cnt].setValue(params);
      if (this.mainLight < 0) {
        this.lightShadowMatrixVsId[cnt].setValue(spot._shadowMatrix.data);
        this.lightShadowParamsVsId[cnt].setValue(params);
        this.lightPosVsId[cnt].setValue(spot._position.data);
        this.mainLight = i;
      }
    }
    if (spot._cookie) {
      this.lightCookieId[cnt].setValue(spot._cookie);
      if (!spot.castShadows) {
        var shadowCam = this.getShadowCamera(this.device, spot);
        var shadowCamNode = shadowCam._node;
        shadowCamNode.setPosition(spot._node.getPosition());
        shadowCamNode.setRotation(spot._node.getRotation());
        shadowCamNode.rotateLocal(-90, 0, 0);
        shadowCam.projection = pc.PROJECTION_PERSPECTIVE;
        shadowCam.aspectRatio = 1;
        shadowCam.fov = spot._outerConeAngle * 2;
        shadowCamView.setTRS(shadowCamNode.getPosition(), shadowCamNode.getRotation(), pc.Vec3.ONE).invert();
        shadowCamViewProj.mul2(shadowCam.getProjectionMatrix(), shadowCamView);
        spot._shadowMatrix.mul2(scaleShift, shadowCamViewProj);
      }
      this.lightShadowMatrixId[cnt].setValue(spot._shadowMatrix.data);
      this.lightCookieIntId[cnt].setValue(spot.cookieIntensity);
      if (spot._cookieTransform) {
        this.lightCookieMatrixId[cnt].setValue(spot._cookieTransform.data);
        this.lightCookieOffsetId[cnt].setValue(spot._cookieOffset.data);
      }
    }
  }, dispatchLocalLights:function(scene, mask, usedDirLights, staticLightList) {
    var i;
    var point, spot;
    var localLights = scene._localLights;
    var pnts = localLights[pc.LIGHTTYPE_POINT - 1];
    var spts = localLights[pc.LIGHTTYPE_SPOT - 1];
    var numDirs = usedDirLights;
    var numPnts = pnts.length;
    var numSpts = spts.length;
    var cnt = numDirs;
    var scope = this.device.scope;
    for (i = 0;i < numPnts;i++) {
      point = pnts[i];
      if (!(point._mask & mask)) {
        continue;
      }
      if (point.isStatic) {
        continue;
      }
      this.dispatchPointLight(scene, scope, point, cnt);
      cnt++;
    }
    var staticId = 0;
    if (staticLightList) {
      point = staticLightList[staticId];
      while (point && point._type === pc.LIGHTTYPE_POINT) {
        if (!(point._mask & mask)) {
          staticId++;
          point = staticLightList[staticId];
          continue;
        }
        this.dispatchPointLight(scene, scope, point, cnt);
        cnt++;
        staticId++;
        point = staticLightList[staticId];
      }
    }
    for (i = 0;i < numSpts;i++) {
      spot = spts[i];
      if (!(spot._mask & mask)) {
        continue;
      }
      if (spot.isStatic) {
        continue;
      }
      this.dispatchSpotLight(scene, scope, spot, cnt);
      cnt++;
    }
    if (staticLightList) {
      spot = staticLightList[staticId];
      while (spot) {
        if (!(spot._mask & mask)) {
          staticId++;
          spot = staticLightList[staticId];
          continue;
        }
        this.dispatchSpotLight(scene, scope, spot, cnt);
        cnt++;
        staticId++;
        spot = staticLightList[staticId];
      }
    }
  }, cull:function(camera, drawCalls) {
    culled.length = 0;
    var i, drawCall, visible;
    var drawCallsCount = drawCalls.length;
    var cullingMask = camera.cullingMask || 4294967295;
    if (!camera.frustumCulling) {
      for (i = 0;i < drawCallsCount;i++) {
        drawCall = drawCalls[i];
        if (!drawCall.visible && !drawCall.command) {
          continue;
        }
        if (drawCall.mask && (drawCall.mask & cullingMask) === 0) {
          continue;
        }
        culled.push(drawCall);
      }
      return culled;
    }
    for (i = 0;i < drawCallsCount;i++) {
      drawCall = drawCalls[i];
      if (!drawCall.command) {
        if (!drawCall.visible) {
          continue;
        }
        visible = true;
        if (drawCall.mask && (drawCall.mask & cullingMask) === 0) {
          continue;
        }
        if (drawCall.layer > pc.LAYER_FX) {
          if (drawCall.cull) {
            visible = this._isVisible(camera, drawCall);
          }
        }
        if (visible) {
          culled.push(drawCall);
        }
      } else {
        culled.push(drawCall);
      }
    }
    return culled;
  }, calculateSortDistances:function(drawCalls, camPos, camFwd, frontToBack) {
    var i, drawCall, btype, meshPos;
    var tempx, tempy, tempz;
    var drawCallsCount = drawCalls.length;
    for (i = 0;i < drawCallsCount;i++) {
      drawCall = drawCalls[i];
      if (drawCall.command) {
        continue;
      }
      if (drawCall.layer <= pc.scene.LAYER_FX) {
        continue;
      }
      btype = drawCall.material.blendType;
      if (btype !== pc.BLEND_NONE) {
        meshPos = drawCall.aabb.center.data;
        tempx = meshPos[0] - camPos[0];
        tempy = meshPos[1] - camPos[1];
        tempz = meshPos[2] - camPos[2];
        drawCall.zdist = tempx * camFwd[0] + tempy * camFwd[1] + tempz * camFwd[2];
      } else {
        if (drawCall.zdist !== undefined) {
          delete drawCall.zdist;
        }
      }
      if (frontToBack && btype === pc.BLEND_NONE) {
        meshPos = drawCall.aabb.center.data;
        tempx = meshPos[0] - camPos[0];
        tempy = meshPos[1] - camPos[1];
        tempz = meshPos[2] - camPos[2];
        drawCall.zdist2 = tempx * camFwd[0] + tempy * camFwd[1] + tempz * camFwd[2];
      }
    }
  }, updateCpuSkinMatrices:function(drawCalls) {
    var drawCallsCount = drawCalls.length;
    if (drawCallsCount === 0) {
      return;
    }
    var i, skin;
    for (i = 0;i < drawCallsCount;i++) {
      skin = drawCalls[i].skinInstance;
      if (skin) {
        skin.updateMatrices();
        skin._dirty = true;
      }
    }
  }, updateGpuSkinMatrices:function(drawCalls) {
    var i, skin;
    var drawCallsCount = drawCalls.length;
    for (i = 0;i < drawCallsCount;i++) {
      skin = drawCalls[i].skinInstance;
      if (skin) {
        if (skin._dirty) {
          skin.updateMatrixPalette();
          skin._dirty = false;
        }
      }
    }
  }, sortDrawCalls:function(drawCalls, sortFunc, keyType) {
    var drawCallsCount = drawCalls.length;
    if (drawCallsCount === 0) {
      return;
    }
    drawCalls.sort(sortFunc);
  }, prepareInstancing:function(device, drawCalls, keyType, shaderType) {
    if (!device.extInstancing) {
      return;
    }
    var drawCallsCount = drawCalls.length;
    var i, j, meshInstance, mesh, next, autoInstances, key, data;
    var offset = 0;
    if (device.enableAutoInstancing) {
      for (i = 0;i < drawCallsCount - 1;i++) {
        meshInstance = drawCalls[i];
        mesh = meshInstance.mesh;
        key = meshInstance._key[keyType];
        next = i + 1;
        autoInstances = 0;
        if (drawCalls[next].mesh === mesh && drawCalls[next]._key[keyType] === key) {
          for (j = 0;j < 16;j++) {
            pc._autoInstanceBufferData[offset + j] = meshInstance.node.worldTransform.data[j];
          }
          autoInstances = 1;
          while (next !== drawCallsCount && drawCalls[next].mesh === mesh && drawCalls[next]._key[keyType] === key) {
            for (j = 0;j < 16;j++) {
              pc._autoInstanceBufferData[offset + autoInstances * 16 + j] = drawCalls[next].node.worldTransform.data[j];
            }
            autoInstances++;
            next++;
          }
          data = meshInstance.instancingData;
          if (!data) {
            meshInstance.instancingData = data = {};
          }
          data.count = autoInstances;
          data.offset = offset * 4;
          data._buffer = pc._autoInstanceBuffer;
          i = next - 1;
        }
        offset += autoInstances * 16;
      }
      if (offset > 0) {
        pc._autoInstanceBuffer.unlock();
      }
    }
    for (i = 0;i < drawCallsCount;i++) {
      meshInstance = drawCalls[i];
      if (meshInstance.instancingData) {
        if (!(meshInstance._shaderDefs & pc.SHADERDEF_INSTANCING)) {
          meshInstance._shaderDefs |= pc.SHADERDEF_INSTANCING;
          meshInstance._shader[shaderType] = null;
        }
        if (!meshInstance.instancingData._buffer) {
          meshInstance.instancingData._buffer = new pc.VertexBuffer(device, pc._instanceVertexFormat, meshInstance.instancingData.count, meshInstance.instancingData.usage, meshInstance.instancingData.buffer);
        }
      } else {
        if (meshInstance._shaderDefs & pc.SHADERDEF_INSTANCING) {
          meshInstance._shaderDefs &= ~pc.SHADERDEF_INSTANCING;
          meshInstance._shader[shaderType] = null;
        }
      }
    }
  }, setBaseConstants:function(device, material) {
    device.setCullMode(material.cull);
    if (material.opacityMap) {
      this.opacityMapId.setValue(material.opacityMap);
      this.alphaTestId.setValue(material.alphaTest);
    }
  }, setSkinning:function(device, meshInstance, material) {
    if (meshInstance.skinInstance) {
      this._skinDrawCalls++;
      this.skinPosOffsetId.setValue(meshInstance.skinInstance.rootNode.getPosition().data);
      if (device.supportsBoneTextures) {
        boneTexture = meshInstance.skinInstance.boneTexture;
        this.boneTextureId.setValue(boneTexture);
        boneTextureSize[0] = boneTexture.width;
        boneTextureSize[1] = boneTexture.height;
        this.boneTextureSizeId.setValue(boneTextureSize);
      } else {
        this.poseMatrixId.setValue(meshInstance.skinInstance.matrixPalette);
      }
    }
  }, drawInstance:function(device, meshInstance, mesh, style, normal) {
    instancingData = meshInstance.instancingData;
    if (instancingData) {
      this._instancedDrawCalls++;
      this._removedByInstancing += instancingData.count;
      device.setVertexBuffer(instancingData._buffer, 1, instancingData.offset);
      device.draw(mesh.primitive[style], instancingData.count);
      if (instancingData._buffer === pc._autoInstanceBuffer) {
        meshInstance.instancingData = null;
        return instancingData.count - 1;
      }
    } else {
      modelMatrix = meshInstance.node.worldTransform;
      this.modelMatrixId.setValue(modelMatrix.data);
      if (normal) {
        normalMatrix = meshInstance.normalMatrix;
        modelMatrix.invertTo3x3(normalMatrix);
        normalMatrix.transpose();
        this.normalMatrixId.setValue(normalMatrix.data);
      }
      device.draw(mesh.primitive[style]);
      return 0;
    }
  }, drawInstance2:function(device, meshInstance, mesh, style) {
    instancingData = meshInstance.instancingData;
    if (instancingData) {
      this._instancedDrawCalls++;
      this._removedByInstancing += instancingData.count;
      device.setVertexBuffer(instancingData._buffer, 1, instancingData.offset);
      device.draw(mesh.primitive[style], instancingData.count);
      if (instancingData._buffer === pc._autoInstanceBuffer) {
        meshInstance.instancingData = null;
        return instancingData.count - 1;
      }
    } else {
      device.draw(mesh.primitive[style]);
      return 0;
    }
  }, findShadowShader:function(meshInstance, type, shadowType) {
    if (shadowType >= numShadowModes) {
      shadowType -= numShadowModes;
    }
    var material = meshInstance.material;
    return this.library.getProgram("depthrgba", {skin:!!meshInstance.skinInstance, opacityMap:!!material.opacityMap, opacityChannel:material.opacityMap ? material.opacityMapChannel || "r" : null, point:type !== pc.LIGHTTYPE_DIRECTIONAL, shadowType:shadowType, instancing:meshInstance.instancingData});
  }, renderShadows:function(device, camera, drawCalls, lights) {
    var i, j, light, shadowShader, type, shadowCam, shadowCamNode, lightNode, passes, pass, frustumSize, shadowType, smode;
    var unitPerTexel, delta, p;
    var minx, miny, minz, maxx, maxy, maxz, centerx, centery;
    var opChan;
    var visible, cullTime, numInstances;
    var meshInstance, mesh, material;
    var style;
    var emptyAabb;
    var drawCallAabb;
    for (i = 0;i < lights.length;i++) {
      light = lights[i];
      type = light._type;
      if (light.castShadows && light._enabled && light.shadowUpdateMode !== pc.SHADOWUPDATE_NONE) {
        shadowCam = this.getShadowCamera(device, light);
        shadowCamNode = shadowCam._node;
        lightNode = light._node;
        passes = 1;
        shadowCamNode.setPosition(lightNode.getPosition());
        shadowCamNode.setRotation(lightNode.getRotation());
        shadowCamNode.rotateLocal(-90, 0, 0);
        if (type === pc.LIGHTTYPE_DIRECTIONAL) {
          _getFrustumPoints(camera, light.shadowDistance || camera._farClip, frustumPoints);
          frustumSize = frustumDiagonal.sub2(frustumPoints[0], frustumPoints[6]).length();
          frustumSize = Math.max(frustumSize, frustumDiagonal.sub2(frustumPoints[4], frustumPoints[6]).length());
          shadowCamView.copy(shadowCamNode.getWorldTransform()).invert();
          c2sc.copy(shadowCamView).mul(camera._node.worldTransform);
          for (j = 0;j < 8;j++) {
            c2sc.transformPoint(frustumPoints[j], frustumPoints[j]);
          }
          minx = miny = minz = 1E6;
          maxx = maxy = maxz = -1E6;
          for (j = 0;j < 8;j++) {
            p = frustumPoints[j];
            if (p.x < minx) {
              minx = p.x;
            }
            if (p.x > maxx) {
              maxx = p.x;
            }
            if (p.y < miny) {
              miny = p.y;
            }
            if (p.y > maxy) {
              maxy = p.y;
            }
            if (p.z < minz) {
              minz = p.z;
            }
            if (p.z > maxz) {
              maxz = p.z;
            }
          }
          unitPerTexel = frustumSize / light._shadowResolution;
          delta = (frustumSize - (maxx - minx)) * .5;
          minx = Math.floor((minx - delta) / unitPerTexel) * unitPerTexel;
          delta = (frustumSize - (maxy - miny)) * .5;
          miny = Math.floor((miny - delta) / unitPerTexel) * unitPerTexel;
          maxx = minx + frustumSize;
          maxy = miny + frustumSize;
          centerx = (maxx + minx) * .5;
          centery = (maxy + miny) * .5;
          shadowCamNode.translateLocal(centerx, centery, 1E5);
          shadowCam.projection = pc.PROJECTION_ORTHOGRAPHIC;
          shadowCam.nearClip = 0;
          shadowCam.farClip = 2E5;
          shadowCam.aspectRatio = 1;
          shadowCam.orthoHeight = frustumSize * .5;
        } else {
          if (type === pc.LIGHTTYPE_SPOT) {
            if (camera.frustumCulling) {
              light.getBoundingSphere(tempSphere);
              if (!camera.frustum.containsSphere(tempSphere)) {
                continue;
              }
            }
            shadowCam.projection = pc.PROJECTION_PERSPECTIVE;
            shadowCam.nearClip = light.attenuationEnd / 1E3;
            shadowCam.farClip = light.attenuationEnd;
            shadowCam.aspectRatio = 1;
            shadowCam.fov = light._outerConeAngle * 2;
            this.viewPosId.setValue(shadowCamNode.getPosition().data);
            this.shadowMapLightRadiusId.setValue(light.attenuationEnd);
          } else {
            if (type === pc.LIGHTTYPE_POINT) {
              if (camera.frustumCulling) {
                light.getBoundingSphere(tempSphere);
                if (!camera.frustum.containsSphere(tempSphere)) {
                  continue;
                }
              }
              shadowCam.projection = pc.PROJECTION_PERSPECTIVE;
              shadowCam.nearClip = light.attenuationEnd / 1E3;
              shadowCam.farClip = light.attenuationEnd;
              shadowCam.aspectRatio = 1;
              shadowCam.fov = 90;
              passes = 6;
              this.viewPosId.setValue(shadowCamNode.getPosition().data);
              this.shadowMapLightRadiusId.setValue(light.attenuationEnd);
            }
          }
        }
        if (light.shadowUpdateMode === pc.SHADOWUPDATE_THISFRAME) {
          light.shadowUpdateMode = pc.SHADOWUPDATE_NONE;
        }
        this._shadowMapUpdates += passes;
        for (pass = 0;pass < passes;pass++) {
          if (type === pc.LIGHTTYPE_POINT) {
            if (pass === 0) {
              shadowCamNode.setEulerAngles(0, 90, 180);
            } else {
              if (pass === 1) {
                shadowCamNode.setEulerAngles(0, -90, 180);
              } else {
                if (pass === 2) {
                  shadowCamNode.setEulerAngles(90, 0, 0);
                } else {
                  if (pass === 3) {
                    shadowCamNode.setEulerAngles(-90, 0, 0);
                  } else {
                    if (pass === 4) {
                      shadowCamNode.setEulerAngles(0, 180, 180);
                    } else {
                      if (pass === 5) {
                        shadowCamNode.setEulerAngles(0, 0, 180);
                      }
                    }
                  }
                }
              }
            }
            shadowCamNode.setPosition(lightNode.getPosition());
            shadowCam.renderTarget = light._shadowCubeMap[pass];
          }
          this.setCamera(shadowCam, type !== pc.LIGHTTYPE_POINT);
          culled.length = 0;
          for (j = 0, numInstances = drawCalls.length;j < numInstances;j++) {
            meshInstance = drawCalls[j];
            visible = true;
            if (meshInstance.cull) {
              visible = this._isVisible(shadowCam, meshInstance);
            }
            if (visible) {
              culled.push(meshInstance);
            }
          }
          this.updateGpuSkinMatrices(culled);
          shadowType = light._shadowType;
          smode = shadowType + (type !== pc.LIGHTTYPE_DIRECTIONAL ? numShadowModes : 0);
          this.sortDrawCalls(culled, this.depthSortCompare, pc.SORTKEY_DEPTH);
          this.prepareInstancing(device, culled, pc.SORTKEY_DEPTH, pc.SHADER_SHADOW + smode);
          if (type === pc.LIGHTTYPE_DIRECTIONAL) {
            emptyAabb = true;
            for (j = 0;j < culled.length;j++) {
              meshInstance = culled[j];
              drawCallAabb = meshInstance.aabb;
              if (emptyAabb) {
                visibleSceneAabb.copy(drawCallAabb);
                emptyAabb = false;
              } else {
                visibleSceneAabb.add(drawCallAabb);
              }
            }
            var z = _getZFromAABBSimple(shadowCamView, visibleSceneAabb.getMin(), visibleSceneAabb.getMax(), minx, maxx, miny, maxy);
            maxz = z.max;
            if (z.min > minz) {
              minz = z.min;
            }
            shadowCamNode.setPosition(lightNode.getPosition());
            shadowCamNode.translateLocal(centerx, centery, maxz + directionalShadowEpsilon);
            shadowCam.farClip = maxz - minz;
            this.setCamera(shadowCam, true);
          }
          if (type !== pc.LIGHTTYPE_POINT) {
            shadowCamView.setTRS(shadowCamNode.getPosition(), shadowCamNode.getRotation(), pc.Vec3.ONE).invert();
            shadowCamViewProj.mul2(shadowCam.getProjectionMatrix(), shadowCamView);
            light._shadowMatrix.mul2(scaleShift, shadowCamViewProj);
          }
          device.setBlending(false);
          device.setColorWrite(true, true, true, true);
          device.setDepthWrite(true);
          device.setDepthTest(true);
          if (device.extDepthTexture) {
            device.setColorWrite(false, false, false, false);
          }
          for (j = 0, numInstances = culled.length;j < numInstances;j++) {
            meshInstance = culled[j];
            mesh = meshInstance.mesh;
            material = meshInstance.material;
            this.setBaseConstants(device, material);
            this.setSkinning(device, meshInstance, material);
            shadowShader = meshInstance._shader[pc.SHADER_SHADOW + smode];
            if (!shadowShader) {
              shadowShader = this.findShadowShader(meshInstance, type, shadowType);
              meshInstance._shader[pc.SHADER_SHADOW + smode] = shadowShader;
              meshInstance._key[pc.SORTKEY_DEPTH] = getDepthKey(meshInstance);
            }
            device.setShader(shadowShader);
            style = meshInstance.renderStyle;
            device.setVertexBuffer(mesh.vertexBuffer, 0);
            device.setIndexBuffer(mesh.indexBuffer[style]);
            j += this.drawInstance(device, meshInstance, mesh, style);
            this._shadowDrawCalls++;
          }
        }
        if (light._shadowType > pc.SHADOW_DEPTH) {
          var filterSize = light._vsmBlurSize;
          if (filterSize > 1) {
            var origShadowMap = shadowCam.renderTarget;
            var tempRt = getShadowMapFromCache(device, light._shadowResolution, light._shadowType, 1);
            var blurMode = light.vsmBlurMode;
            var blurShader = (light._shadowType === pc.SHADOW_VSM8 ? this.blurPackedVsmShader : this.blurVsmShader)[blurMode][filterSize];
            if (!blurShader) {
              this.blurVsmWeights[filterSize] = gaussWeights(filterSize);
              var chunks = pc.shaderChunks;
              (light._shadowType === pc.SHADOW_VSM8 ? this.blurPackedVsmShader : this.blurVsmShader)[blurMode][filterSize] = blurShader = chunks.createShaderFromCode(this.device, chunks.fullscreenQuadVS, "#define SAMPLES " + filterSize + "\n" + (light._shadowType === pc.SHADOW_VSM8 ? this.blurPackedVsmShaderCode : this.blurVsmShaderCode)[blurMode], "blurVsm" + blurMode + "" + filterSize + "" + (light._shadowType === pc.SHADOW_VSM8));
            }
            blurScissorRect.z = light._shadowResolution - 2;
            blurScissorRect.w = blurScissorRect.z;
            this.sourceId.setValue(origShadowMap.colorBuffer);
            pixelOffset.x = 1 / light._shadowResolution;
            pixelOffset.y = 0;
            this.pixelOffsetId.setValue(pixelOffset.data);
            if (blurMode === pc.BLUR_GAUSSIAN) {
              this.weightId.setValue(this.blurVsmWeights[filterSize]);
            }
            pc.drawQuadWithShader(device, tempRt, blurShader, null, blurScissorRect);
            this.sourceId.setValue(tempRt.colorBuffer);
            pixelOffset.y = pixelOffset.x;
            pixelOffset.x = 0;
            this.pixelOffsetId.setValue(pixelOffset.data);
            pc.drawQuadWithShader(device, origShadowMap, blurShader, null, blurScissorRect);
          }
        }
      }
    }
  }, findDepthShader:function(meshInstance) {
    var material = meshInstance.material;
    return this.library.getProgram("depth", {skin:!!meshInstance.skinInstance, opacityMap:!!material.opacityMap, opacityChannel:material.opacityMap ? material.opacityMapChannel || "r" : null, instancing:meshInstance.instancingData});
  }, filterDepthMapDrawCalls:function(drawCalls) {
    filtered.length = 0;
    var meshInstance;
    for (var i = 0;i < drawCalls.length;i++) {
      meshInstance = drawCalls[i];
      if (!meshInstance.command && meshInstance.drawToDepth && meshInstance.material.blendType === pc.BLEND_NONE) {
        filtered.push(meshInstance);
      }
    }
    return filtered;
  }, renderDepth:function(device, camera, drawCalls) {
    if (camera._renderDepthRequests) {
      var i;
      var shadowType;
      var rect = camera._rect;
      var width = Math.floor(rect.width * device.width);
      var height = Math.floor(rect.height * device.height);
      var meshInstance, mesh, material, style, depthShader;
      var vrDisplay = camera.vrDisplay;
      var halfWidth = device.width * .5;
      drawCalls = this.filterDepthMapDrawCalls(drawCalls);
      var drawCallsCount = drawCalls.length;
      this.sortDrawCalls(drawCalls, this.depthSortCompare, pc.SORTKEY_DEPTH);
      this.prepareInstancing(device, drawCalls, pc.SORTKEY_DEPTH, pc.SHADER_DEPTH);
      if (camera._depthTarget && (camera._depthTarget.width !== width || camera._depthTarget.height !== height)) {
        camera._depthTarget.destroy();
        camera._depthTarget = null;
      }
      if (!camera._depthTarget) {
        var colorBuffer = new pc.Texture(device, {format:pc.PIXELFORMAT_R8_G8_B8_A8, width:width, height:height});
        colorBuffer.minFilter = pc.FILTER_NEAREST;
        colorBuffer.magFilter = pc.FILTER_NEAREST;
        colorBuffer.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
        colorBuffer.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
        camera._depthTarget = new pc.RenderTarget(device, colorBuffer, {depth:true, stencil:device.supportsStencil});
      }
      var oldTarget = camera.renderTarget;
      camera.renderTarget = camera._depthTarget;
      this.setCamera(camera);
      device.setBlending(false);
      device.setColorWrite(true, true, true, true);
      device.setDepthWrite(true);
      device.setDepthTest(true);
      for (i = 0;i < drawCallsCount;i++) {
        meshInstance = drawCalls[i];
        mesh = meshInstance.mesh;
        material = meshInstance.material;
        this.setBaseConstants(device, material);
        this.setSkinning(device, meshInstance, material);
        depthShader = meshInstance._shader[pc.SHADER_DEPTH];
        if (!depthShader) {
          depthShader = this.findDepthShader(meshInstance);
          meshInstance._shader[pc.SHADER_DEPTH] = depthShader;
          meshInstance._key[pc.SORTKEY_DEPTH] = getDepthKey(meshInstance);
        }
        device.setShader(depthShader);
        style = meshInstance.renderStyle;
        device.setVertexBuffer(mesh.vertexBuffer, 0);
        device.setIndexBuffer(mesh.indexBuffer[style]);
        if (vrDisplay && vrDisplay.presenting) {
          device.setViewport(0, 0, halfWidth, device.height);
          this.viewProjId.setValue(viewProjMatL.data);
          this.viewPosId.setValue(viewPosL.data);
          i += this.drawInstance(device, meshInstance, mesh, style, true);
          this._forwardDrawCalls++;
          device.setViewport(halfWidth, 0, halfWidth, device.height);
          this.viewProjId.setValue(viewProjMatR.data);
          this.viewPosId.setValue(viewPosR.data);
          i += this.drawInstance2(device, meshInstance, mesh, style);
          this._forwardDrawCalls++;
        } else {
          i += this.drawInstance(device, meshInstance, mesh, style);
          this._depthDrawCalls++;
        }
      }
      camera.renderTarget = oldTarget;
    } else {
      if (camera._depthTarget) {
        camera._depthTarget.destroy();
        camera._depthTarget = null;
      }
    }
  }, renderForward:function(device, camera, drawCalls, scene) {
    var drawCallsCount = drawCalls.length;
    var vrDisplay = camera.vrDisplay;
    this.sortDrawCalls(drawCalls, this.frontToBack ? this.sortCompare : this.sortCompareMesh, pc.SORTKEY_FORWARD);
    this.prepareInstancing(device, drawCalls, pc.SORTKEY_FORWARD, pc.SHADER_FORWARD);
    var i, drawCall, mesh, material, objDefs, lightMask, style, usedDirLights;
    var prevMeshInstance = null, prevMaterial = null, prevObjDefs, prevLightMask, prevStatic;
    var paramName, parameter, parameters;
    var stencilFront, stencilBack;
    this.setCamera(camera);
    this.dispatchGlobalLights(scene);
    if (scene.fog !== pc.FOG_NONE) {
      this.fogColor[0] = scene.fogColor.data[0];
      this.fogColor[1] = scene.fogColor.data[1];
      this.fogColor[2] = scene.fogColor.data[2];
      if (scene.gammaCorrection) {
        for (i = 0;i < 3;i++) {
          this.fogColor[i] = Math.pow(this.fogColor[i], 2.2);
        }
      }
      this.fogColorId.setValue(this.fogColor);
      if (scene.fog === pc.FOG_LINEAR) {
        this.fogStartId.setValue(scene.fogStart);
        this.fogEndId.setValue(scene.fogEnd);
      } else {
        this.fogDensityId.setValue(scene.fogDensity);
      }
    }
    this._screenSize.x = device.width;
    this._screenSize.y = device.height;
    this._screenSize.z = 1 / device.width;
    this._screenSize.w = 1 / device.height;
    this.screenSizeId.setValue(this._screenSize.data);
    var halfWidth = device.width * .5;
    if (camera._depthTarget) {
      this.depthMapId.setValue(camera._depthTarget.colorBuffer);
    }
    for (i = 0;i < drawCallsCount;i++) {
      drawCall = drawCalls[i];
      if (drawCall.command) {
        drawCall.command();
      } else {
        mesh = drawCall.mesh;
        material = drawCall.material;
        objDefs = drawCall._shaderDefs;
        lightMask = drawCall.mask;
        this.setSkinning(device, drawCall, material);
        if (material && material === prevMaterial && objDefs !== prevObjDefs) {
          prevMaterial = null;
        }
        if (drawCall.isStatic || prevStatic) {
          prevMaterial = null;
        }
        if (material !== prevMaterial) {
          this._materialSwitches++;
          if (!drawCall._shader[pc.SHADER_FORWARD] || drawCall._shaderDefs !== objDefs) {
            if (!drawCall.isStatic) {
              drawCall._shader[pc.SHADER_FORWARD] = material.variants[objDefs];
              if (!drawCall._shader[pc.SHADER_FORWARD]) {
                material.updateShader(device, scene, objDefs);
                drawCall._shader[pc.SHADER_FORWARD] = material.variants[objDefs] = material.shader;
              }
            } else {
              material.updateShader(device, scene, objDefs, drawCall._staticLightList);
              drawCall._shader[pc.SHADER_FORWARD] = material.shader;
            }
            drawCall._shaderDefs = objDefs;
          }
          device.setShader(drawCall._shader[pc.SHADER_FORWARD]);
          parameters = material.parameters;
          for (paramName in parameters) {
            parameter = parameters[paramName];
            if (!parameter.scopeId) {
              parameter.scopeId = device.scope.resolve(paramName);
            }
            parameter.scopeId.setValue(parameter.data);
          }
          if (!prevMaterial || lightMask !== prevLightMask) {
            usedDirLights = this.dispatchDirectLights(scene, lightMask);
            this.dispatchLocalLights(scene, lightMask, usedDirLights, drawCall._staticLightList);
          }
          this.alphaTestId.setValue(material.alphaTest);
          device.setBlending(material.blend);
          device.setBlendFunction(material.blendSrc, material.blendDst);
          device.setBlendEquation(material.blendEquation);
          device.setColorWrite(material.redWrite, material.greenWrite, material.blueWrite, material.alphaWrite);
          device.setCullMode(material.cull);
          device.setDepthWrite(material.depthWrite);
          device.setDepthTest(material.depthTest);
          stencilFront = material.stencilFront;
          stencilBack = material.stencilBack;
          if (stencilFront || stencilBack) {
            device.setStencilTest(true);
            if (stencilFront === stencilBack) {
              device.setStencilFunc(stencilFront.func, stencilFront.ref, stencilFront.mask);
              device.setStencilOperation(stencilFront.fail, stencilFront.zfail, stencilFront.zpass);
            } else {
              if (stencilFront) {
                device.setStencilFuncFront(stencilFront.func, stencilFront.ref, stencilFront.mask);
                device.setStencilOperationFront(stencilFront.fail, stencilFront.zfail, stencilFront.zpass);
              } else {
                device.setStencilFuncFront(pc.FUNC_ALWAYS, 0, 255);
                device.setStencilOperationFront(pc.STENCILOP_KEEP, pc.STENCILOP_KEEP, pc.STENCILOP_KEEP);
              }
              if (stencilBack) {
                device.setStencilFuncBack(stencilBack.func, stencilBack.ref, stencilBack.mask);
                device.setStencilOperationBack(stencilBack.fail, stencilBack.zfail, stencilBack.zpass);
              } else {
                device.setStencilFuncBack(pc.FUNC_ALWAYS, 0, 255);
                device.setStencilOperationBack(pc.STENCILOP_KEEP, pc.STENCILOP_KEEP, pc.STENCILOP_KEEP);
              }
            }
          } else {
            device.setStencilTest(false);
          }
        }
        parameters = drawCall.parameters;
        for (paramName in parameters) {
          parameter = parameters[paramName];
          if (!parameter.scopeId) {
            parameter.scopeId = device.scope.resolve(paramName);
          }
          parameter.scopeId.setValue(parameter.data);
        }
        device.setVertexBuffer(mesh.vertexBuffer, 0);
        style = drawCall.renderStyle;
        device.setIndexBuffer(mesh.indexBuffer[style]);
        if (vrDisplay && vrDisplay.presenting) {
          device.setViewport(0, 0, halfWidth, device.height);
          this.projId.setValue(projL.data);
          this.viewInvId.setValue(viewInvL.data);
          this.viewId.setValue(viewL.data);
          this.viewId3.setValue(viewMat3L.data);
          this.viewProjId.setValue(viewProjMatL.data);
          this.viewPosId.setValue(viewPosL.data);
          i += this.drawInstance(device, drawCall, mesh, style, true);
          this._forwardDrawCalls++;
          device.setViewport(halfWidth, 0, halfWidth, device.height);
          this.projId.setValue(projR.data);
          this.viewInvId.setValue(viewInvR.data);
          this.viewId.setValue(viewR.data);
          this.viewId3.setValue(viewMat3R.data);
          this.viewProjId.setValue(viewProjMatR.data);
          this.viewPosId.setValue(viewPosR.data);
          i += this.drawInstance2(device, drawCall, mesh, style);
          this._forwardDrawCalls++;
        } else {
          i += this.drawInstance(device, drawCall, mesh, style, true);
          this._forwardDrawCalls++;
        }
        if (i < drawCallsCount - 1 && drawCalls[i + 1].material === material) {
          for (paramName in parameters) {
            parameter = material.parameters[paramName];
            if (parameter) {
              parameter.scopeId.setValue(parameter.data);
            }
          }
        }
        prevMaterial = material;
        prevMeshInstance = drawCall;
        prevObjDefs = objDefs;
        prevLightMask = lightMask;
        prevStatic = drawCall.isStatic;
      }
    }
    device.setStencilTest(false);
  }, sortLights:function(scene) {
    var light;
    var lights = scene._lights;
    scene._globalLights.length = 0;
    scene._localLights[0].length = 0;
    scene._localLights[1].length = 0;
    for (i = 0;i < lights.length;i++) {
      light = lights[i];
      if (light._enabled) {
        if (light._type === pc.LIGHTTYPE_DIRECTIONAL) {
          scene._globalLights.push(light);
        } else {
          scene._localLights[light._type === pc.LIGHTTYPE_POINT ? 0 : 1].push(light);
        }
      }
    }
    return lights;
  }, setupInstancing:function(device) {
    if (!pc._instanceVertexFormat) {
      var formatDesc = [{semantic:pc.SEMANTIC_TEXCOORD2, components:4, type:pc.ELEMENTTYPE_FLOAT32}, {semantic:pc.SEMANTIC_TEXCOORD3, components:4, type:pc.ELEMENTTYPE_FLOAT32}, {semantic:pc.SEMANTIC_TEXCOORD4, components:4, type:pc.ELEMENTTYPE_FLOAT32}, {semantic:pc.SEMANTIC_TEXCOORD5, components:4, type:pc.ELEMENTTYPE_FLOAT32}];
      pc._instanceVertexFormat = new pc.VertexFormat(device, formatDesc);
    }
    if (device.enableAutoInstancing) {
      if (!pc._autoInstanceBuffer) {
        pc._autoInstanceBuffer = new pc.VertexBuffer(device, pc._instanceVertexFormat, device.autoInstancingMaxObjects, pc.BUFFER_DYNAMIC);
        pc._autoInstanceBufferData = new Float32Array(pc._autoInstanceBuffer.lock());
      }
    }
  }, prepareStaticMeshes:function(device, scene) {
    var i, j, k, v, s, index;
    var drawCalls = scene.drawCalls;
    var lights = scene._lights;
    var drawCallsCount = drawCalls.length;
    var drawCall, light;
    var newDrawCalls = [];
    if (!scene._needsStaticPrepare) {
      var prevStaticSource;
      for (i = 0;i < drawCallsCount;i++) {
        drawCall = drawCalls[i];
        if (drawCall._staticSource) {
          if (drawCall._staticSource !== prevStaticSource) {
            newDrawCalls.push(drawCall._staticSource);
            prevStaticSource = drawCall._staticSource;
          }
        } else {
          newDrawCalls.push(drawCall);
        }
      }
      drawCalls = newDrawCalls;
      drawCallsCount = drawCalls.length;
      newDrawCalls = [];
    }
    var mesh;
    var indices, verts, numTris, elems, vertSize, offsetP, baseIndex;
    var _x, _y, _z;
    var minx, miny, minz, maxx, maxy, maxz;
    var minv, maxv;
    var minVec = new pc.Vec3;
    var maxVec = new pc.Vec3;
    var triAabb = new pc.BoundingBox;
    var localLightBounds = new pc.BoundingBox;
    var invMatrix = new pc.Mat4;
    var triLightComb = [];
    var triLightCombUsed;
    var indexBuffer, vertexBuffer;
    var combIndices, combIbName, combIb;
    var lightTypePass;
    var lightAabb = [];
    var aabb;
    var triBounds = [];
    var staticLights = [];
    var bit;
    for (i = 0;i < drawCallsCount;i++) {
      drawCall = drawCalls[i];
      if (!drawCall.isStatic) {
        newDrawCalls.push(drawCall);
      } else {
        aabb = drawCall.aabb;
        staticLights.length = 0;
        for (lightTypePass = pc.LIGHTTYPE_POINT;lightTypePass <= pc.LIGHTTYPE_SPOT;lightTypePass++) {
          for (j = 0;j < lights.length;j++) {
            light = lights[j];
            if (light._type !== lightTypePass) {
              continue;
            }
            if (light._enabled) {
              if (light._mask & drawCall.mask) {
                if (light.isStatic) {
                  if (!lightAabb[j]) {
                    lightAabb[j] = new pc.BoundingBox;
                    light._node.getWorldTransform();
                    light.getBoundingSphere(tempSphere);
                    lightAabb[j].center.copy(tempSphere.center);
                    lightAabb[j].halfExtents.x = tempSphere.radius;
                    lightAabb[j].halfExtents.y = tempSphere.radius;
                    lightAabb[j].halfExtents.z = tempSphere.radius;
                  }
                  if (!lightAabb[j].intersects(aabb)) {
                    continue;
                  }
                  staticLights.push(j);
                }
              }
            }
          }
        }
        if (staticLights.length === 0) {
          newDrawCalls.push(drawCall);
          continue;
        }
        mesh = drawCall.mesh;
        vertexBuffer = mesh.vertexBuffer;
        indexBuffer = mesh.indexBuffer[drawCall.renderStyle];
        indices = new Uint16Array(indexBuffer.lock());
        numTris = mesh.primitive[drawCall.renderStyle].count / 3;
        baseIndex = mesh.primitive[drawCall.renderStyle].base;
        elems = vertexBuffer.format.elements;
        vertSize = vertexBuffer.format.size / 4;
        verts = new Float32Array(vertexBuffer.storage);
        for (k = 0;k < elems.length;k++) {
          if (elems[k].name === pc.SEMANTIC_POSITION) {
            offsetP = elems[k].offset / 4;
          }
        }
        triLightComb.length = numTris;
        for (k = 0;k < numTris;k++) {
          triLightComb[k] = 0;
        }
        triLightCombUsed = false;
        triBounds.length = numTris * 6;
        for (k = 0;k < numTris;k++) {
          minx = Number.MAX_VALUE;
          miny = Number.MAX_VALUE;
          minz = Number.MAX_VALUE;
          maxx = -Number.MAX_VALUE;
          maxy = -Number.MAX_VALUE;
          maxz = -Number.MAX_VALUE;
          for (v = 0;v < 3;v++) {
            index = indices[k * 3 + v + baseIndex];
            index = index * vertSize + offsetP;
            _x = verts[index];
            _y = verts[index + 1];
            _z = verts[index + 2];
            if (_x < minx) {
              minx = _x;
            }
            if (_y < miny) {
              miny = _y;
            }
            if (_z < minz) {
              minz = _z;
            }
            if (_x > maxx) {
              maxx = _x;
            }
            if (_y > maxy) {
              maxy = _y;
            }
            if (_z > maxz) {
              maxz = _z;
            }
          }
          index = k * 6;
          triBounds[index] = minx;
          triBounds[index + 1] = miny;
          triBounds[index + 2] = minz;
          triBounds[index + 3] = maxx;
          triBounds[index + 4] = maxy;
          triBounds[index + 5] = maxz;
        }
        for (s = 0;s < staticLights.length;s++) {
          j = staticLights[s];
          light = lights[j];
          invMatrix.copy(drawCall.node.worldTransform).invert();
          localLightBounds.setFromTransformedAabb(lightAabb[j], invMatrix);
          minv = localLightBounds.getMin().data;
          maxv = localLightBounds.getMax().data;
          bit = 1 << s;
          for (k = 0;k < numTris;k++) {
            index = k * 6;
            if (triBounds[index] <= maxv[0] && triBounds[index + 3] >= minv[0] && triBounds[index + 1] <= maxv[1] && triBounds[index + 4] >= minv[1] && triBounds[index + 2] <= maxv[2] && triBounds[index + 5] >= minv[2]) {
              triLightComb[k] |= bit;
              triLightCombUsed = true;
            }
          }
        }
        if (triLightCombUsed) {
          combIndices = {};
          for (k = 0;k < numTris;k++) {
            j = k * 3 + baseIndex;
            combIbName = triLightComb[k];
            if (!combIndices[combIbName]) {
              combIndices[combIbName] = [];
            }
            combIb = combIndices[combIbName];
            combIb.push(indices[j]);
            combIb.push(indices[j + 1]);
            combIb.push(indices[j + 2]);
          }
          for (combIbName in combIndices) {
            combIb = combIndices[combIbName];
            var ib = new pc.IndexBuffer(device, indexBuffer.format, combIb.length, indexBuffer.usage);
            var ib2 = new Uint16Array(ib.lock());
            ib2.set(combIb);
            ib.unlock();
            minx = Number.MAX_VALUE;
            miny = Number.MAX_VALUE;
            minz = Number.MAX_VALUE;
            maxx = -Number.MAX_VALUE;
            maxy = -Number.MAX_VALUE;
            maxz = -Number.MAX_VALUE;
            for (k = 0;k < combIb.length;k++) {
              index = combIb[k];
              _x = verts[index * vertSize + offsetP];
              _y = verts[index * vertSize + offsetP + 1];
              _z = verts[index * vertSize + offsetP + 2];
              if (_x < minx) {
                minx = _x;
              }
              if (_y < miny) {
                miny = _y;
              }
              if (_z < minz) {
                minz = _z;
              }
              if (_x > maxx) {
                maxx = _x;
              }
              if (_y > maxy) {
                maxy = _y;
              }
              if (_z > maxz) {
                maxz = _z;
              }
            }
            minVec.set(minx, miny, minz);
            maxVec.set(maxx, maxy, maxz);
            var chunkAabb = new pc.BoundingBox;
            chunkAabb.setMinMax(minVec, maxVec);
            var mesh2 = new pc.Mesh;
            mesh2.vertexBuffer = vertexBuffer;
            mesh2.indexBuffer[0] = ib;
            mesh2.primitive[0].type = pc.PRIMITIVE_TRIANGLES;
            mesh2.primitive[0].base = 0;
            mesh2.primitive[0].count = combIb.length;
            mesh2.primitive[0].indexed = true;
            mesh2.aabb = chunkAabb;
            var instance = new pc.MeshInstance(drawCall.node, mesh2, drawCall.material);
            instance.isStatic = drawCall.isStatic;
            instance.visible = drawCall.visible;
            instance.layer = drawCall.layer;
            instance.castShadow = drawCall.castShadow;
            instance._receiveShadow = drawCall._receiveShadow;
            instance.drawToDepth = drawCall.drawToDepth;
            instance.cull = drawCall.cull;
            instance.pick = drawCall.pick;
            instance.mask = drawCall.mask;
            instance.parameters = drawCall.parameters;
            instance._shaderDefs = drawCall._shaderDefs;
            instance._staticSource = drawCall;
            instance._staticLightList = [];
            for (k = 0;k < staticLights.length;k++) {
              bit = 1 << k;
              if (combIbName & bit) {
                instance._staticLightList.push(lights[staticLights[k]]);
              }
            }
            instance._staticLightList.sort(this.lightCompare);
            newDrawCalls.push(instance);
          }
        } else {
          newDrawCalls.push(drawCall);
        }
      }
    }
    scene.drawCalls = newDrawCalls;
  }, render:function(scene, camera) {
    var device = this.device;
    scene._activeCamera = camera;
    if (scene.updateShaders) {
      scene.updateShadersFunc(device);
      scene.updateShaders = false;
    }
    if (scene._needsStaticPrepare) {
      this.prepareStaticMeshes(device, scene);
      scene._needsStaticPrepare = false;
    }
    var target = camera.renderTarget;
    var isHdr = false;
    var oldGamma = scene._gammaCorrection;
    var oldTonemap = scene._toneMapping;
    var oldExposure = scene.exposure;
    if (target) {
      var format = target.colorBuffer.format;
      if (format === pc.PIXELFORMAT_RGB16F || format === pc.PIXELFORMAT_RGB32F) {
        isHdr = true;
        scene._gammaCorrection = pc.GAMMA_NONE;
        scene._toneMapping = pc.TONEMAP_LINEAR;
        scene.exposure = 1;
      }
    }
    var i;
    var drawCalls = scene.drawCalls;
    var shadowCasters = scene.shadowCasters;
    var lights = this.sortLights(scene);
    var camPos = camera._node.getPosition().data;
    var camFwd = camera._node.forward.data;
    this.setupInstancing(device);
    this.updateCameraFrustum(camera);
    this.updateCpuSkinMatrices(drawCalls);
    this.renderShadows(device, camera, shadowCasters, lights);
    drawCalls = this.cull(camera, drawCalls);
    this.calculateSortDistances(drawCalls, camPos, camFwd, this.frontToBack);
    this.updateGpuSkinMatrices(drawCalls);
    for (i = 0;i < scene.immediateDrawCalls.length;i++) {
      drawCalls.push(scene.immediateDrawCalls[i]);
    }
    this._immediateRendered += scene.immediateDrawCalls.length;
    this.renderDepth(device, camera, drawCalls);
    this.renderForward(device, camera, drawCalls, scene);
    device.setColorWrite(true, true, true, true);
    if (scene.immediateDrawCalls.length > 0) {
      scene.immediateDrawCalls = [];
    }
    if (isHdr) {
      scene._gammaCorrection = oldGamma;
      scene._toneMapping = oldTonemap;
      scene.exposure = oldExposure;
    }
    this._camerasRendered++;
  }});
  return {ForwardRenderer:ForwardRenderer, gaussWeights:gaussWeights};
}());
pc.extend(pc, function() {
  var GraphNode = function GraphNode() {
    this.name = "Untitled";
    this.tags = new pc.Tags(this);
    this._labels = {};
    this.localPosition = new pc.Vec3(0, 0, 0);
    this.localRotation = new pc.Quat(0, 0, 0, 1);
    this.localScale = new pc.Vec3(1, 1, 1);
    this.localEulerAngles = new pc.Vec3(0, 0, 0);
    this.position = new pc.Vec3(0, 0, 0);
    this.rotation = new pc.Quat(0, 0, 0, 1);
    this.eulerAngles = new pc.Vec3(0, 0, 0);
    this.localTransform = new pc.Mat4;
    this.dirtyLocal = false;
    this._aabbVer = 0;
    this.worldTransform = new pc.Mat4;
    this.dirtyWorld = false;
    this._right = new pc.Vec3;
    this._up = new pc.Vec3;
    this._forward = new pc.Vec3;
    this._parent = null;
    this._children = [];
    this._enabled = true;
    this._enabledInHierarchy = false;
  };
  Object.defineProperty(GraphNode.prototype, "right", {get:function() {
    return this.getWorldTransform().getX(this._right).normalize();
  }});
  Object.defineProperty(GraphNode.prototype, "up", {get:function() {
    return this.getWorldTransform().getY(this._up).normalize();
  }});
  Object.defineProperty(GraphNode.prototype, "forward", {get:function() {
    return this.getWorldTransform().getZ(this._forward).normalize().scale(-1);
  }});
  Object.defineProperty(GraphNode.prototype, "enabled", {get:function() {
    return this._enabled && this._enabledInHierarchy;
  }, set:function(enabled) {
    if (this._enabled !== enabled) {
      this._enabled = enabled;
      if (!this._parent || this._parent.enabled) {
        this._notifyHierarchyStateChanged(this, enabled);
      }
    }
  }});
  Object.defineProperty(GraphNode.prototype, "parent", {get:function() {
    return this._parent;
  }});
  Object.defineProperty(GraphNode.prototype, "root", {get:function() {
    var parent = this._parent;
    if (!parent) {
      return this;
    }
    while (parent._parent) {
      parent = parent._parent;
    }
    return parent;
  }});
  Object.defineProperty(GraphNode.prototype, "children", {get:function() {
    return this._children;
  }});
  pc.extend(GraphNode.prototype, {_notifyHierarchyStateChanged:function(node, enabled) {
    node._onHierarchyStateChanged(enabled);
    var c = node._children;
    for (var i = 0, len = c.length;i < len;i++) {
      if (c[i]._enabled) {
        this._notifyHierarchyStateChanged(c[i], enabled);
      }
    }
  }, _onHierarchyStateChanged:function(enabled) {
    this._enabledInHierarchy = enabled;
  }, _cloneInternal:function(clone) {
    clone.name = this.name;
    var tags = this.tags._list;
    for (var i = 0;i < tags.length;i++) {
      clone.tags.add(tags[i]);
    }
    clone._labels = pc.extend(this._labels, {});
    clone.localPosition.copy(this.localPosition);
    clone.localRotation.copy(this.localRotation);
    clone.localScale.copy(this.localScale);
    clone.localEulerAngles.copy(this.localEulerAngles);
    clone.position.copy(this.position);
    clone.rotation.copy(this.rotation);
    clone.eulerAngles.copy(this.eulerAngles);
    clone.localTransform.copy(this.localTransform);
    clone.dirtyLocal = this.dirtyLocal;
    clone.worldTransform.copy(this.worldTransform);
    clone.dirtyWorld = this.dirtyWorld;
    clone._aabbVer = this._aabbVer + 1;
    clone._enabled = this._enabled;
    clone._enabledInHierarchy = false;
  }, clone:function() {
    var clone = new pc.GraphNode;
    this._cloneInternal(clone);
    return clone;
  }, find:function(attr, value) {
    var results = [];
    var len = this._children.length;
    var i, descendants;
    if (attr instanceof Function) {
      var fn = attr;
      for (i = 0;i < len;i++) {
        if (fn(this._children[i])) {
          results.push(this._children[i]);
        }
        descendants = this._children[i].find(fn);
        if (descendants.length) {
          results = results.concat(descendants);
        }
      }
    } else {
      var testValue;
      if (this[attr]) {
        if (this[attr] instanceof Function) {
          testValue = this[attr]();
        } else {
          testValue = this[attr];
        }
        if (testValue === value) {
          results.push(this);
        }
      }
      for (i = 0;i < len;++i) {
        descendants = this._children[i].find(attr, value);
        if (descendants.length) {
          results = results.concat(descendants);
        }
      }
    }
    return results;
  }, findOne:function(attr, value) {
    var i;
    var len = this._children.length;
    var result = null;
    if (attr instanceof Function) {
      var fn = attr;
      result = fn(this);
      if (result) {
        return this;
      }
      for (i = 0;i < len;i++) {
        result = this._children[i].findOne(fn);
        if (result) {
          return this._children[i];
        }
      }
    } else {
      var testValue;
      if (this[attr]) {
        if (this[attr] instanceof Function) {
          testValue = this[attr]();
        } else {
          testValue = this[attr];
        }
        if (testValue === value) {
          return this;
        }
      }
      for (i = 0;i < len;i++) {
        result = this._children[i].findOne(attr, value);
        if (result !== null) {
          return result;
        }
      }
    }
    return null;
  }, findByTag:function() {
    var tags = this.tags._processArguments(arguments);
    return this._findByTag(tags);
  }, _findByTag:function(tags) {
    var result = [];
    var i, len = this._children.length;
    var descendants;
    for (i = 0;i < len;i++) {
      if (this._children[i].tags._has(tags)) {
        result.push(this._children[i]);
      }
      descendants = this._children[i]._findByTag(tags);
      if (descendants.length) {
        result = result.concat(descendants);
      }
    }
    return result;
  }, findByName:function(name) {
    if (this.name === name) {
      return this;
    }
    for (var i = 0;i < this._children.length;i++) {
      var found = this._children[i].findByName(name);
      if (found !== null) {
        return found;
      }
    }
    return null;
  }, findByPath:function(path) {
    var parts = path.split("/");
    var currentParent = this;
    var result = null;
    for (var i = 0, imax = parts.length;i < imax && currentParent;i++) {
      var part = parts[i];
      result = null;
      var children = currentParent._children;
      for (var j = 0, jmax = children.length;j < jmax;j++) {
        if (children[j].name == part) {
          result = children[j];
          break;
        }
      }
      currentParent = result;
    }
    return result;
  }, getPath:function() {
    var parent = this._parent;
    if (parent) {
      var path = this.name;
      var format = "{0}/{1}";
      while (parent && parent._parent) {
        path = pc.string.format(format, parent.name, path);
        parent = parent._parent;
      }
      return path;
    } else {
      return "";
    }
  }, getRoot:function() {
    var parent = this._parent;
    if (!parent) {
      return this;
    }
    while (parent._parent) {
      parent = parent._parent;
    }
    return parent;
  }, getParent:function() {
    return this._parent;
  }, isDescendantOf:function(node) {
    var parent = this._parent;
    while (parent) {
      if (parent === node) {
        return true;
      }
      parent = parent._parent;
    }
    return false;
  }, isAncestorOf:function(node) {
    return node.isDescendantOf(this);
  }, getChildren:function() {
    return this._children;
  }, getEulerAngles:function() {
    this.getWorldTransform().getEulerAngles(this.eulerAngles);
    return this.eulerAngles;
  }, getLocalEulerAngles:function() {
    this.localRotation.getEulerAngles(this.localEulerAngles);
    return this.localEulerAngles;
  }, getLocalPosition:function() {
    return this.localPosition;
  }, getLocalRotation:function() {
    return this.localRotation;
  }, getLocalScale:function() {
    return this.localScale;
  }, getLocalTransform:function() {
    if (this.dirtyLocal) {
      this.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);
      this.dirtyLocal = false;
      this.dirtyWorld = true;
      this._aabbVer++;
    }
    return this.localTransform;
  }, getName:function() {
    return this.name;
  }, getPosition:function() {
    this.getWorldTransform().getTranslation(this.position);
    return this.position;
  }, getRotation:function() {
    this.rotation.setFromMat4(this.getWorldTransform());
    return this.rotation;
  }, getWorldTransform:function() {
    var syncList = [];
    return function() {
      var current = this;
      syncList.length = 0;
      while (current !== null) {
        syncList.push(current);
        current = current._parent;
      }
      for (var i = syncList.length - 1;i >= 0;i--) {
        syncList[i].sync();
      }
      return this.worldTransform;
    };
  }(), reparent:function(parent, index) {
    var current = this._parent;
    if (current) {
      current.removeChild(this);
    }
    if (parent) {
      if (index >= 0) {
        parent.insertChild(this, index);
      } else {
        parent.addChild(this);
      }
    }
  }, setLocalEulerAngles:function(x, y, z) {
    if (x instanceof pc.Vec3) {
      this.localRotation.setFromEulerAngles(x.data[0], x.data[1], x.data[2]);
    } else {
      this.localRotation.setFromEulerAngles(x, y, z);
    }
    this.dirtyLocal = true;
  }, setLocalPosition:function(x, y, z) {
    if (x instanceof pc.Vec3) {
      this.localPosition.copy(x);
    } else {
      this.localPosition.set(x, y, z);
    }
    this.dirtyLocal = true;
  }, setLocalRotation:function(x, y, z, w) {
    if (x instanceof pc.Quat) {
      this.localRotation.copy(x);
    } else {
      this.localRotation.set(x, y, z, w);
    }
    this.dirtyLocal = true;
  }, setLocalScale:function(x, y, z) {
    if (x instanceof pc.Vec3) {
      this.localScale.copy(x);
    } else {
      this.localScale.set(x, y, z);
    }
    this.dirtyLocal = true;
  }, setName:function(name) {
    this.name = name;
  }, setPosition:function() {
    var position = new pc.Vec3;
    var invParentWtm = new pc.Mat4;
    return function(x, y, z) {
      if (x instanceof pc.Vec3) {
        position.copy(x);
      } else {
        position.set(x, y, z);
      }
      if (this._parent === null) {
        this.localPosition.copy(position);
      } else {
        invParentWtm.copy(this._parent.getWorldTransform()).invert();
        invParentWtm.transformPoint(position, this.localPosition);
      }
      this.dirtyLocal = true;
    };
  }(), setRotation:function() {
    var rotation = new pc.Quat;
    var invParentRot = new pc.Quat;
    return function(x, y, z, w) {
      if (x instanceof pc.Quat) {
        rotation.copy(x);
      } else {
        rotation.set(x, y, z, w);
      }
      if (this._parent === null) {
        this.localRotation.copy(rotation);
      } else {
        var parentRot = this._parent.getRotation();
        invParentRot.copy(parentRot).invert();
        this.localRotation.copy(invParentRot).mul(rotation);
      }
      this.dirtyLocal = true;
    };
  }(), setEulerAngles:function() {
    var invParentRot = new pc.Quat;
    return function(x, y, z) {
      if (x instanceof pc.Vec3) {
        this.localRotation.setFromEulerAngles(x.data[0], x.data[1], x.data[2]);
      } else {
        this.localRotation.setFromEulerAngles(x, y, z);
      }
      if (this._parent !== null) {
        var parentRot = this._parent.getRotation();
        invParentRot.copy(parentRot).invert();
        this.localRotation.mul2(invParentRot, this.localRotation);
      }
      this.dirtyLocal = true;
    };
  }(), addChild:function(node) {
    if (node._parent !== null) {
      throw new Error("GraphNode is already parented");
    }
    this._children.push(node);
    this._onInsertChild(node);
  }, addChildAndSaveTransform:function(node) {
    var wPos = node.getPosition();
    var wRot = node.getRotation();
    var current = node._parent;
    if (current) {
      current.removeChild(node);
    }
    if (this.tmpMat4 === undefined) {
      this.tmpMat4 = new pc.Mat4;
      this.tmpQuat = new pc.Quat;
    }
    node.setPosition(this.tmpMat4.copy(this.worldTransform).invert().transformPoint(wPos));
    node.setRotation(this.tmpQuat.copy(this.getRotation()).invert().mul(wRot));
    this._children.push(node);
    this._onInsertChild(node);
  }, insertChild:function(node, index) {
    if (node._parent !== null) {
      throw new Error("GraphNode is already parented");
    }
    this._children.splice(index, 0, node);
    this._onInsertChild(node);
  }, _onInsertChild:function(node) {
    node._parent = this;
    var enabledInHierarchy = node._enabled && this.enabled;
    if (node._enabledInHierarchy !== enabledInHierarchy) {
      node._enabledInHierarchy = enabledInHierarchy;
      node._notifyHierarchyStateChanged(node, enabledInHierarchy);
    }
    node.dirtyWorld = true;
    node._aabbVer++;
    if (node.fire) {
      node.fire("insert", this);
    }
  }, removeChild:function(child) {
    var i;
    var length = this._children.length;
    for (i = 0;i < length;++i) {
      if (this._children[i] === child) {
        this._children.splice(i, 1);
        child._parent = null;
        return;
      }
    }
  }, addLabel:function(label) {
    this._labels[label] = true;
  }, getLabels:function() {
    return Object.keys(this._labels);
  }, hasLabel:function(label) {
    return !!this._labels[label];
  }, removeLabel:function(label) {
    delete this._labels[label];
  }, findByLabel:function(label, results) {
    var i, length = this._children.length;
    results = results || [];
    if (this.hasLabel(label)) {
      results.push(this);
    }
    for (i = 0;i < length;++i) {
      results = this._children[i].findByLabel(label, results);
    }
    return results;
  }, sync:function() {
    if (this.dirtyLocal) {
      this.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);
      this.dirtyLocal = false;
      this.dirtyWorld = true;
      this._aabbVer++;
    }
    if (this.dirtyWorld) {
      if (this._parent === null) {
        this.worldTransform.copy(this.localTransform);
      } else {
        this.worldTransform.mul2(this._parent.worldTransform, this.localTransform);
      }
      this.dirtyWorld = false;
      var child;
      for (var i = 0, len = this._children.length;i < len;i++) {
        child = this._children[i];
        child.dirtyWorld = true;
        child._aabbVer++;
      }
    }
  }, syncHierarchy:function() {
    var F = function() {
      if (!this._enabled) {
        return;
      }
      this.sync();
      var c = this._children;
      for (var i = 0, len = c.length;i < len;i++) {
        F.call(c[i]);
      }
    };
    return F;
  }(), lookAt:function() {
    var matrix = new pc.Mat4;
    var target = new pc.Vec3;
    var up = new pc.Vec3;
    var rotation = new pc.Quat;
    return function(tx, ty, tz, ux, uy, uz) {
      if (tx instanceof pc.Vec3) {
        target.copy(tx);
        if (ty instanceof pc.Vec3) {
          up.copy(ty);
        } else {
          up.copy(pc.Vec3.UP);
        }
      } else {
        if (tz === undefined) {
          return;
        } else {
          target.set(tx, ty, tz);
          if (ux !== undefined) {
            up.set(ux, uy, uz);
          } else {
            up.copy(pc.Vec3.UP);
          }
        }
      }
      matrix.setLookAt(this.getPosition(), target, up);
      rotation.setFromMat4(matrix);
      this.setRotation(rotation);
    };
  }(), translate:function() {
    var translation = new pc.Vec3;
    return function(x, y, z) {
      if (x instanceof pc.Vec3) {
        translation.copy(x);
      } else {
        translation.set(x, y, z);
      }
      translation.add(this.getPosition());
      this.setPosition(translation);
    };
  }(), translateLocal:function() {
    var translation = new pc.Vec3;
    return function(x, y, z) {
      if (x instanceof pc.Vec3) {
        translation.copy(x);
      } else {
        translation.set(x, y, z);
      }
      this.localRotation.transformVector(translation, translation);
      this.localPosition.add(translation);
      this.dirtyLocal = true;
    };
  }(), rotate:function() {
    var quaternion = new pc.Quat;
    var invParentRot = new pc.Quat;
    return function(x, y, z) {
      if (x instanceof pc.Vec3) {
        quaternion.setFromEulerAngles(x.data[0], x.data[1], x.data[2]);
      } else {
        quaternion.setFromEulerAngles(x, y, z);
      }
      if (this._parent === null) {
        this.localRotation.mul2(quaternion, this.localRotation);
      } else {
        var rot = this.getRotation();
        var parentRot = this._parent.getRotation();
        invParentRot.copy(parentRot).invert();
        quaternion.mul2(invParentRot, quaternion);
        this.localRotation.mul2(quaternion, rot);
      }
      this.dirtyLocal = true;
    };
  }(), rotateLocal:function() {
    var quaternion = new pc.Quat;
    return function(x, y, z) {
      if (x instanceof pc.Vec3) {
        quaternion.setFromEulerAngles(x.data[0], x.data[1], x.data[2]);
      } else {
        quaternion.setFromEulerAngles(x, y, z);
      }
      this.localRotation.mul(quaternion);
      this.dirtyLocal = true;
    };
  }()});
  return {GraphNode:GraphNode};
}());
pc.extend(pc, function() {
  var _deviceCoord = new pc.Vec3;
  var _far = new pc.Vec3;
  var Camera = function() {
    this._projection = pc.PROJECTION_PERSPECTIVE;
    this._nearClip = .1;
    this._farClip = 1E4;
    this._fov = 45;
    this._orthoHeight = 10;
    this._aspect = 16 / 9;
    this._horizontalFov = false;
    this.frustumCulling = false;
    this.cullingMask = 4294967295;
    this._renderDepthRequests = 0;
    this._projMatDirty = true;
    this._projMat = new pc.Mat4;
    this._viewMat = new pc.Mat4;
    this._viewProjMat = new pc.Mat4;
    this.vrDisplay = null;
    this._rect = {x:0, y:0, width:1, height:1};
    this.frustum = new pc.Frustum(this._projMat, this._viewMat);
    this.renderTarget = null;
    this._depthTarget = null;
    this._clearOptions = {color:[.5, .5, .5, 1], depth:1, stencil:0, flags:pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH | pc.CLEARFLAG_STENCIL};
    this._node = null;
  };
  Camera.prototype = {clone:function() {
    var clone = new pc.Camera;
    clone.projection = this._projection;
    clone.nearClip = this._nearClip;
    clone.farClip = this._farClip;
    clone.fov = this._fov;
    clone.aspectRatio = this._aspect;
    clone.renderTarget = this.renderTarget;
    clone.setClearOptions(this.getClearOptions());
    clone.frustumCulling = this.frustumCulling;
    clone.cullingMask = this.cullingMask;
    return clone;
  }, worldToScreen:function(worldCoord, cw, ch, screenCoord) {
    if (screenCoord === undefined) {
      screenCoord = new pc.Vec3;
    }
    var projMat = this.getProjectionMatrix();
    var wtm = this._node.getWorldTransform();
    this._viewMat.copy(wtm).invert();
    this._viewProjMat.mul2(projMat, this._viewMat);
    this._viewProjMat.transformPoint(worldCoord, screenCoord);
    var wp = worldCoord.data;
    var vpm = this._viewProjMat.data;
    var w = wp[0] * vpm[3] + wp[1] * vpm[7] + wp[2] * vpm[11] + 1 * vpm[15];
    screenCoord.x = (screenCoord.x / w + 1) * .5 * cw;
    screenCoord.y = (1 - screenCoord.y / w) * .5 * ch;
    return screenCoord;
  }, screenToWorld:function(x, y, z, cw, ch, worldCoord) {
    if (worldCoord === undefined) {
      worldCoord = new pc.Vec3;
    }
    var projMat = this.getProjectionMatrix();
    var wtm = this._node.getWorldTransform();
    this._viewMat.copy(wtm).invert();
    this._viewProjMat.mul2(projMat, this._viewMat);
    var invViewProjMat = this._viewProjMat.clone().invert();
    if (this._projection === pc.PROJECTION_PERSPECTIVE) {
      _far.set(x / cw * 2 - 1, (ch - y) / ch * 2 - 1, 1);
      var farW = invViewProjMat.transformPoint(_far);
      var w = _far.x * invViewProjMat.data[3] + _far.y * invViewProjMat.data[7] + _far.z * invViewProjMat.data[11] + invViewProjMat.data[15];
      farW.scale(1 / w);
      var alpha = z / this._farClip;
      worldCoord.lerp(this._node.getPosition(), farW, alpha);
    } else {
      var range = this._farClip - this._nearClip;
      _deviceCoord.set(x / cw * 2 - 1, (ch - y) / ch * 2 - 1, (this._farClip - z) / range * 2 - 1);
      invViewProjMat.transformPoint(_deviceCoord, worldCoord);
    }
    return worldCoord;
  }, getClearOptions:function() {
    return this._clearOptions;
  }, getProjectionMatrix:function() {
    if (this._projMatDirty) {
      if (this._projection === pc.PROJECTION_PERSPECTIVE) {
        this._projMat.setPerspective(this._fov, this._aspect, this._nearClip, this._farClip, this._horizontalFov);
      } else {
        var y = this._orthoHeight;
        var x = y * this._aspect;
        this._projMat.setOrtho(-x, x, -y, y, this._nearClip, this._farClip);
      }
      this._projMatDirty = false;
    }
    return this._projMat;
  }, getRect:function() {
    return this._rect;
  }, setClearOptions:function(options) {
    this._clearOptions.color[0] = options.color[0];
    this._clearOptions.color[1] = options.color[1];
    this._clearOptions.color[2] = options.color[2];
    this._clearOptions.color[3] = options.color[3];
    this._clearOptions.depth = options.depth;
    this._clearOptions.stencil = options.stencil;
    this._clearOptions.flags = options.flags;
  }, setRect:function(x, y, width, height) {
    this._rect.x = x;
    this._rect.y = y;
    this._rect.width = width;
    this._rect.height = height;
  }, requestDepthMap:function() {
    this._renderDepthRequests++;
  }, releaseDepthMap:function() {
    this._renderDepthRequests--;
  }};
  Object.defineProperty(Camera.prototype, "aspectRatio", {get:function() {
    return this._aspect;
  }, set:function(v) {
    if (this._aspect !== v) {
      this._aspect = v;
      this._projMatDirty = true;
    }
  }});
  Object.defineProperty(Camera.prototype, "projection", {get:function() {
    return this._projection;
  }, set:function(v) {
    if (this._projection !== v) {
      this._projection = v;
      this._projMatDirty = true;
    }
  }});
  Object.defineProperty(Camera.prototype, "nearClip", {get:function() {
    return this._nearClip;
  }, set:function(v) {
    if (this._nearClip !== v) {
      this._nearClip = v;
      this._projMatDirty = true;
    }
  }});
  Object.defineProperty(Camera.prototype, "farClip", {get:function() {
    return this._farClip;
  }, set:function(v) {
    if (this._farClip !== v) {
      this._farClip = v;
      this._projMatDirty = true;
    }
  }});
  Object.defineProperty(Camera.prototype, "fov", {get:function() {
    return this._fov;
  }, set:function(v) {
    if (this._fov !== v) {
      this._fov = v;
      this._projMatDirty = true;
    }
  }});
  Object.defineProperty(Camera.prototype, "horizontalFov", {get:function() {
    return this._horizontalFov;
  }, set:function(v) {
    if (this._horizontalFov !== v) {
      this._horizontalFov = v;
      this._projMatDirty = true;
    }
  }});
  Object.defineProperty(Camera.prototype, "orthoHeight", {get:function() {
    return this._orthoHeight;
  }, set:function(v) {
    if (this._orthoHeight !== v) {
      this._orthoHeight = v;
      this._projMatDirty = true;
    }
  }});
  Object.defineProperty(Camera.prototype, "clearColor", {get:function() {
    return this._clearOptions.color;
  }, set:function(v) {
    this._clearOptions.color[0] = v[0];
    this._clearOptions.color[1] = v[1];
    this._clearOptions.color[2] = v[2];
    this._clearOptions.color[3] = v[3];
  }});
  Object.defineProperty(Camera.prototype, "clearDepth", {get:function() {
    return this._clearOptions.depth;
  }, set:function(v) {
    this._clearOptions.depth = v;
  }});
  Object.defineProperty(Camera.prototype, "clearStencil", {get:function() {
    return this._clearOptions.stencil;
  }, set:function(v) {
    this._clearOptions.stencil = v;
  }});
  Object.defineProperty(Camera.prototype, "clearFlags", {get:function() {
    return this._clearOptions.flags;
  }, set:function(v) {
    this._clearOptions.flags = v;
  }});
  return {Camera:Camera};
}());
pc.extend(pc, function() {
  var spotCenter = new pc.Vec3;
  var spotEndPoint = new pc.Vec3;
  var tmpVec = new pc.Vec3;
  var chanId = {r:0, g:1, b:2, a:3};
  var Light = function Light() {
    this._type = pc.LIGHTTYPE_DIRECTIONAL;
    this._color = new pc.Color(.8, .8, .8);
    this._intensity = 1;
    this._castShadows = false;
    this._enabled = false;
    this._mask = 1;
    this.isStatic = false;
    this.key = 0;
    this.bakeDir = true;
    this.attenuationStart = 10;
    this.attenuationEnd = 10;
    this._falloffMode = 0;
    this._shadowType = pc.SHADOW_DEPTH;
    this._vsmBlurSize = 11;
    this.vsmBlurMode = pc.BLUR_GAUSSIAN;
    this.vsmBias = .01 * .25;
    this._cookie = null;
    this.cookieIntensity = 1;
    this._cookieFalloff = true;
    this._cookieChannel = "rgb";
    this._cookieTransform = null;
    this._cookieOffset = null;
    this._cookieTransformSet = false;
    this._cookieOffsetSet = false;
    this._innerConeAngle = 40;
    this._outerConeAngle = 45;
    this._finalColor = new pc.Vec3(.8, .8, .8);
    var c = Math.pow(this._finalColor.data[0], 2.2);
    this._linearFinalColor = new pc.Vec3(c, c, c);
    this._position = new pc.Vec3(0, 0, 0);
    this._direction = new pc.Vec3(0, 0, 0);
    this._innerConeAngleCos = Math.cos(this._innerConeAngle * Math.PI / 180);
    this._outerConeAngleCos = Math.cos(this._outerConeAngle * Math.PI / 180);
    this._shadowCamera = null;
    this._shadowMatrix = new pc.Mat4;
    this.shadowDistance = 40;
    this._shadowResolution = 1024;
    this.shadowBias = -5E-4;
    this._normalOffsetBias = 0;
    this.shadowUpdateMode = pc.SHADOWUPDATE_REALTIME;
    this._scene = null;
    this._node = null;
    this._rendererParams = [];
  };
  Light.prototype = {clone:function() {
    var clone = new pc.Light;
    clone.type = this._type;
    clone.setColor(this._color);
    clone.intensity = this._intensity;
    clone.castShadows = this.castShadows;
    clone.enabled = this._enabled;
    clone.attenuationStart = this.attenuationStart;
    clone.attenuationEnd = this.attenuationEnd;
    clone.falloffMode = this._falloffMode;
    clone.shadowType = this._shadowType;
    clone.vsmBlurSize = this._vsmBlurSize;
    clone.vsmBlurMode = this.vsmBlurMode;
    clone.vsmBias = this.vsmBias;
    clone.shadowUpdateMode = this.shadowUpdateMode;
    clone.mask = this._mask;
    clone.innerConeAngle = this._innerConeAngle;
    clone.outerConeAngle = this._outerConeAngle;
    clone.shadowBias = this.shadowBias;
    clone.normalOffsetBias = this._normalOffsetBias;
    clone.shadowResolution = this._shadowResolution;
    clone.shadowDistance = this.shadowDistance;
    return clone;
  }, getColor:function() {
    return this._color;
  }, getBoundingSphere:function(sphere) {
    if (this._type === pc.LIGHTTYPE_SPOT) {
      var range = this.attenuationEnd;
      var angle = this._outerConeAngle;
      var f = Math.cos(angle * pc.math.DEG_TO_RAD);
      var node = this._node;
      spotCenter.copy(node.up);
      spotCenter.scale(-range * .5 * f);
      spotCenter.add(node.getPosition());
      sphere.center = spotCenter;
      spotEndPoint.copy(node.up);
      spotEndPoint.scale(-range);
      tmpVec.copy(node.right);
      tmpVec.scale(Math.sin(angle * pc.math.DEG_TO_RAD) * range);
      spotEndPoint.add(tmpVec);
      sphere.radius = spotEndPoint.length() * .5;
    } else {
      if (this._type === pc.LIGHTTYPE_POINT) {
        sphere.center = this._node.getPosition();
        sphere.radius = this.attenuationEnd;
      }
    }
  }, getBoundingBox:function(box) {
    if (this._type === pc.LIGHTTYPE_SPOT) {
      var range = this.attenuationEnd;
      var angle = this._outerConeAngle;
      var node = this._node;
      var scl = Math.abs(Math.sin(angle * pc.math.DEG_TO_RAD) * range);
      box.center.set(0, -range * .5, 0);
      box.halfExtents.set(scl, range * .5, scl);
      box.setFromTransformedAabb(box, node.getWorldTransform());
    } else {
      if (this._type === pc.LIGHTTYPE_POINT) {
        box.center.copy(this._node.getPosition());
        box.halfExtents.set(this.attenuationEnd, this.attenuationEnd, this.attenuationEnd);
      }
    }
  }, setColor:function() {
    var r, g, b;
    if (arguments.length === 1) {
      r = arguments[0].r;
      g = arguments[0].g;
      b = arguments[0].b;
    } else {
      if (arguments.length === 3) {
        r = arguments[0];
        g = arguments[1];
        b = arguments[2];
      }
    }
    this._color.set(r, g, b);
    var i = this._intensity;
    this._finalColor.set(r * i, g * i, b * i);
    for (var c = 0;c < 3;c++) {
      if (i >= 1) {
        this._linearFinalColor.data[c] = Math.pow(this._finalColor.data[c] / i, 2.2) * i;
      } else {
        this._linearFinalColor.data[c] = Math.pow(this._finalColor.data[c], 2.2);
      }
    }
  }, _destroyShadowMap:function() {
    if (this._shadowCamera) {
      var rt = this._shadowCamera.renderTarget;
      var i;
      if (rt) {
        if (rt.length) {
          for (i = 0;i < rt.length;i++) {
            if (rt[i].colorBuffer) {
              rt[i].colorBuffer.destroy();
            }
            rt[i].destroy();
          }
        } else {
          if (rt.colorBuffer) {
            rt.colorBuffer.destroy();
          }
          rt.destroy();
        }
      }
      this._shadowCamera.renderTarget = null;
      this._shadowCamera = null;
      this._shadowCubeMap = null;
      if (this.shadowUpdateMode === pc.SHADOWUPDATE_NONE) {
        this.shadowUpdateMode = pc.SHADOWUPDATE_THISFRAME;
      }
    }
  }, updateShadow:function() {
    if (this.shadowUpdateMode !== pc.SHADOWUPDATE_REALTIME) {
      this.shadowUpdateMode = pc.SHADOWUPDATE_THISFRAME;
    }
  }, updateKey:function() {
    var key = this._type << 29 | (this._castShadows ? 1 : 0) << 28 | this._shadowType << 25 | this._falloffMode << 23 | (this._normalOffsetBias !== 0 ? 1 : 0) << 22 | (this._cookie ? 1 : 0) << 21 | (this._cookieFalloff ? 1 : 0) << 20 | chanId[this._cookieChannel.charAt(0)] << 18 | (this._cookieTransform ? 1 : 0) << 12;
    if (this._cookieChannel.length === 3) {
      key |= chanId[this._cookieChannel.charAt(1)] << 16;
      key |= chanId[this._cookieChannel.charAt(2)] << 14;
    }
    this.key = key;
  }};
  Object.defineProperty(Light.prototype, "enabled", {get:function() {
    return this._type;
  }, set:function(value) {
    if (this._type === value) {
      return;
    }
    this._enabled = value;
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
  }});
  Object.defineProperty(Light.prototype, "type", {get:function() {
    return this._type;
  }, set:function(value) {
    if (this._type === value) {
      return;
    }
    this._type = value;
    this._destroyShadowMap();
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
    this.updateKey();
  }});
  Object.defineProperty(Light.prototype, "mask", {get:function() {
    return this._mask;
  }, set:function(value) {
    if (this._mask === value) {
      return;
    }
    this._mask = value;
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
  }});
  Object.defineProperty(Light.prototype, "shadowType", {get:function() {
    return this._shadowType;
  }, set:function(value) {
    if (this._shadowType === value) {
      return;
    }
    var device = pc.Application.getApplication().graphicsDevice;
    if (this._type === pc.LIGHTTYPE_POINT) {
      value = pc.SHADOW_DEPTH;
    }
    if (value === pc.SHADOW_VSM32 && !device.extTextureFloatRenderable) {
      value = pc.SHADOW_VSM16;
    }
    if (value === pc.SHADOW_VSM16 && !device.extTextureHalfFloatRenderable) {
      value = pc.SHADOW_VSM8;
    }
    this._shadowType = value;
    this._destroyShadowMap();
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
    this.updateKey();
  }});
  Object.defineProperty(Light.prototype, "castShadows", {get:function() {
    return this._castShadows && this._mask !== pc.MASK_LIGHTMAP && this._mask !== 0;
  }, set:function(value) {
    if (this._castShadows === value) {
      return;
    }
    this._castShadows = value;
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
    this.updateKey();
  }});
  Object.defineProperty(Light.prototype, "shadowResolution", {get:function() {
    return this._shadowResolution;
  }, set:function(value) {
    if (this._shadowResolution === value) {
      return;
    }
    var device = pc.Application.getApplication().graphicsDevice;
    if (this._type === pc.LIGHTTYPE_POINT) {
      value = Math.min(value, device.maxCubeMapSize);
    } else {
      value = Math.min(value, device.maxTextureSize);
    }
    this._shadowResolution = value;
  }});
  Object.defineProperty(Light.prototype, "vsmBlurSize", {get:function() {
    return this._vsmBlurSize;
  }, set:function(value) {
    if (this._vsmBlurSize === value) {
      return;
    }
    if (value % 2 === 0) {
      value++;
    }
    this._vsmBlurSize = value;
  }});
  Object.defineProperty(Light.prototype, "normalOffsetBias", {get:function() {
    return this._normalOffsetBias;
  }, set:function(value) {
    if (this._normalOffsetBias === value) {
      return;
    }
    if (!this._normalOffsetBias && value || this._normalOffsetBias && !value) {
      if (this._scene !== null) {
        this._scene.updateShaders = true;
      }
      this.updateKey();
    }
    this._normalOffsetBias = value;
  }});
  Object.defineProperty(Light.prototype, "falloffMode", {get:function() {
    return this._falloffMode;
  }, set:function(value) {
    if (this._falloffMode === value) {
      return;
    }
    this._falloffMode = value;
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
    this.updateKey();
  }});
  Object.defineProperty(Light.prototype, "innerConeAngle", {get:function() {
    return this._innerConeAngle;
  }, set:function(value) {
    if (this._innerConeAngle === value) {
      return;
    }
    this._innerConeAngle = value;
    this._innerConeAngleCos = Math.cos(value * Math.PI / 180);
  }});
  Object.defineProperty(Light.prototype, "outerConeAngle", {get:function() {
    return this._outerConeAngle;
  }, set:function(value) {
    if (this._outerConeAngle === value) {
      return;
    }
    this._outerConeAngle = value;
    this._outerConeAngleCos = Math.cos(value * Math.PI / 180);
  }});
  Object.defineProperty(Light.prototype, "intensity", {get:function() {
    return this._intensity;
  }, set:function(value) {
    if (this._intensity === value) {
      return;
    }
    this._intensity = value;
    var c = this._color.data;
    var r = c[0];
    var g = c[1];
    var b = c[2];
    var i = this._intensity;
    this._finalColor.set(r * i, g * i, b * i);
    for (var j = 0;j < 3;j++) {
      if (i >= 1) {
        this._linearFinalColor.data[j] = Math.pow(this._finalColor.data[j] / i, 2.2) * i;
      } else {
        this._linearFinalColor.data[j] = Math.pow(this._finalColor.data[j], 2.2);
      }
    }
  }});
  Object.defineProperty(Light.prototype, "cookie", {get:function() {
    return this._cookie;
  }, set:function(value) {
    if (this._cookie === value) {
      return;
    }
    this._cookie = value;
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
    this.updateKey();
  }});
  Object.defineProperty(Light.prototype, "cookieFalloff", {get:function() {
    return this._cookieFalloff;
  }, set:function(value) {
    if (this._cookieFalloff === value) {
      return;
    }
    this._cookieFalloff = value;
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
    this.updateKey();
  }});
  Object.defineProperty(Light.prototype, "cookieChannel", {get:function() {
    return this._cookieChannel;
  }, set:function(value) {
    if (this._cookieChannel === value) {
      return;
    }
    if (value.length < 3) {
      var chr = value.charAt(value.length - 1);
      var addLen = 3 - value.length;
      for (var i = 0;i < addLen;i++) {
        value += chr;
      }
    }
    this._cookieChannel = value;
    if (this._scene !== null) {
      this._scene.updateShaders = true;
    }
    this.updateKey();
  }});
  Object.defineProperty(Light.prototype, "cookieTransform", {get:function() {
    return this._cookieTransform;
  }, set:function(value) {
    if (this._cookieTransform === value) {
      return;
    }
    var xformOld = !!(this._cookieTransformSet || this._cookieOffsetSet);
    var xformNew = !!(value || this._cookieOffsetSet);
    if (xformOld !== xformNew) {
      if (this._scene !== null) {
        this._scene.updateShaders = true;
      }
    }
    this._cookieTransform = value;
    this._cookieTransformSet = !!value;
    if (value && !this._cookieOffset) {
      this.cookieOffset = new pc.Vec2;
      this._cookieOffsetSet = false;
    }
    this.updateKey();
  }});
  Object.defineProperty(Light.prototype, "cookieOffset", {get:function() {
    return this._cookieOffset;
  }, set:function(value) {
    if (this._cookieOffset === value) {
      return;
    }
    var xformOld = !!(this._cookieTransformSet || this._cookieOffsetSet);
    var xformNew = !!(this._cookieTransformSet || value);
    if (xformOld !== xformNew) {
      if (this._scene !== null) {
        this._scene.updateShaders = true;
      }
    }
    if (xformNew && !value && this._cookieOffset) {
      this._cookieOffset.set(0, 0);
    } else {
      this._cookieOffset = value;
    }
    this._cookieOffsetSet = !!value;
    if (value && !this._cookieTransform) {
      this.cookieTransform = new pc.Vec4(1, 1, 0, 0);
      this._cookieTransformSet = false;
    }
    this.updateKey();
  }});
  return {Light:Light};
}());
pc.extend(pc, function() {
  var id = 0;
  var Material = function Material() {
    this.name = "Untitled";
    this.id = id++;
    this._shader = null;
    this.variants = {};
    this.parameters = {};
    this.alphaTest = 0;
    this.blend = false;
    this.blendSrc = pc.BLENDMODE_ONE;
    this.blendDst = pc.BLENDMODE_ZERO;
    this.blendEquation = pc.BLENDEQUATION_ADD;
    this.cull = pc.CULLFACE_BACK;
    this.depthTest = true;
    this.depthWrite = true;
    this.stencilFront = null;
    this.stencilBack = null;
    this.redWrite = true;
    this.greenWrite = true;
    this.blueWrite = true;
    this.alphaWrite = true;
    this.meshInstances = [];
  };
  Object.defineProperty(Material.prototype, "shader", {get:function() {
    return this._shader;
  }, set:function(shader) {
    this.setShader(shader);
  }});
  Object.defineProperty(Material.prototype, "blendType", {get:function() {
    if (!this.blend && this.blendSrc === pc.BLENDMODE_ONE && this.blendDst === pc.BLENDMODE_ZERO && this.blendEquation === pc.BLENDEQUATION_ADD) {
      return pc.BLEND_NONE;
    } else {
      if (this.blend && this.blendSrc === pc.BLENDMODE_SRC_ALPHA && this.blendDst === pc.BLENDMODE_ONE_MINUS_SRC_ALPHA && this.blendEquation === pc.BLENDEQUATION_ADD) {
        return pc.BLEND_NORMAL;
      } else {
        if (this.blend && this.blendSrc === pc.BLENDMODE_ONE && this.blendDst === pc.BLENDMODE_ONE && this.blendEquation === pc.BLENDEQUATION_ADD) {
          return pc.BLEND_ADDITIVE;
        } else {
          if (this.blend && this.blendSrc === pc.BLENDMODE_SRC_ALPHA && this.blendDst === pc.BLENDMODE_ONE && this.blendEquation === pc.BLENDEQUATION_ADD) {
            return pc.BLEND_ADDITIVEALPHA;
          } else {
            if (this.blend && this.blendSrc === pc.BLENDMODE_DST_COLOR && this.blendDst === pc.BLENDMODE_SRC_COLOR && this.blendEquation === pc.BLENDEQUATION_ADD) {
              return pc.BLEND_MULTIPLICATIVE2X;
            } else {
              if (this.blend && this.blendSrc === pc.BLENDMODE_ONE_MINUS_DST_COLOR && this.blendDst === pc.BLENDMODE_ONE && this.blendEquation === pc.BLENDEQUATION_ADD) {
                return pc.BLEND_SCREEN;
              } else {
                if (this.blend && this.blendSrc === pc.BLENDMODE_DST_COLOR && this.blendDst === pc.BLENDMODE_ZERO && this.blendEquation === pc.BLENDEQUATION_ADD) {
                  return pc.BLEND_MULTIPLICATIVE;
                } else {
                  if (this.blend && this.blendSrc === pc.BLENDMODE_ONE && this.blendDst === pc.BLENDMODE_ONE_MINUS_SRC_ALPHA && this.blendEquation === pc.BLENDEQUATION_ADD) {
                    return pc.BLEND_PREMULTIPLIED;
                  } else {
                    return pc.BLEND_NORMAL;
                  }
                }
              }
            }
          }
        }
      }
    }
  }, set:function(type) {
    switch(type) {
      case pc.BLEND_NONE:
        this.blend = false;
        this.blendSrc = pc.BLENDMODE_ONE;
        this.blendDst = pc.BLENDMODE_ZERO;
        this.blendEquation = pc.BLENDEQUATION_ADD;
        break;
      case pc.BLEND_NORMAL:
        this.blend = true;
        this.blendSrc = pc.BLENDMODE_SRC_ALPHA;
        this.blendDst = pc.BLENDMODE_ONE_MINUS_SRC_ALPHA;
        this.blendEquation = pc.BLENDEQUATION_ADD;
        break;
      case pc.BLEND_PREMULTIPLIED:
        this.blend = true;
        this.blendSrc = pc.BLENDMODE_ONE;
        this.blendDst = pc.BLENDMODE_ONE_MINUS_SRC_ALPHA;
        this.blendEquation = pc.BLENDEQUATION_ADD;
        break;
      case pc.BLEND_ADDITIVE:
        this.blend = true;
        this.blendSrc = pc.BLENDMODE_ONE;
        this.blendDst = pc.BLENDMODE_ONE;
        this.blendEquation = pc.BLENDEQUATION_ADD;
        break;
      case pc.BLEND_ADDITIVEALPHA:
        this.blend = true;
        this.blendSrc = pc.BLENDMODE_SRC_ALPHA;
        this.blendDst = pc.BLENDMODE_ONE;
        this.blendEquation = pc.BLENDEQUATION_ADD;
        break;
      case pc.BLEND_MULTIPLICATIVE2X:
        this.blend = true;
        this.blendSrc = pc.BLENDMODE_DST_COLOR;
        this.blendDst = pc.BLENDMODE_SRC_COLOR;
        this.blendEquation = pc.BLENDEQUATION_ADD;
        break;
      case pc.BLEND_SCREEN:
        this.blend = true;
        this.blendSrc = pc.BLENDMODE_ONE_MINUS_DST_COLOR;
        this.blendDst = pc.BLENDMODE_ONE;
        this.blendEquation = pc.BLENDEQUATION_ADD;
        break;
      case pc.BLEND_MULTIPLICATIVE:
        this.blend = true;
        this.blendSrc = pc.BLENDMODE_DST_COLOR;
        this.blendDst = pc.BLENDMODE_ZERO;
        this.blendEquation = pc.BLENDEQUATION_ADD;
        break;
    }
    this._updateMeshInstanceKeys();
  }});
  Material.prototype._cloneInternal = function(clone) {
    clone.name = this.name;
    clone.id = id++;
    clone.variants = {};
    clone.shader = this.shader;
    clone.parameters = {};
    for (var parameterName in this.parameters) {
      if (this.parameters.hasOwnProperty(parameterName)) {
        clone.parameters[parameterName] = {scopeId:null, data:this.parameters[parameterName].data};
      }
    }
    clone.alphaTest = this.alphaTest;
    clone.blend = this.blend;
    clone.blendSrc = this.blendSrc;
    clone.blendDst = this.blendDst;
    clone.blendEquation = this.blendEquation;
    clone.cull = this.cull;
    clone.depthTest = this.depthTest;
    clone.depthWrite = this.depthWrite;
    if (this.stencilFront) {
      clone.stencilFront = this.stencilFront.clone();
    }
    if (this.stencilBack) {
      if (this.stencilFront === this.stencilBack) {
        clone.stencilBack = clone.stencilFront;
      } else {
        clone.stencilBack = this.stencilBack.clone();
      }
    }
    clone.redWrite = this.redWrite;
    clone.greenWrite = this.greenWrite;
    clone.blueWrite = this.blueWrite;
    clone.alphaWrite = this.alphaWrite;
    clone.meshInstances = [];
  };
  Material.prototype.clone = function() {
    var clone = new pc.Material;
    this._cloneInternal(clone);
    return clone;
  };
  Material.prototype._updateMeshInstanceKeys = function() {
    var i, meshInstances = this.meshInstances;
    for (i = 0;i < meshInstances.length;i++) {
      meshInstances[i].updateKey();
    }
  };
  Material.prototype.updateShader = function(device, scene, objDefs) {
  };
  Material.prototype.clearParameters = function() {
    this.parameters = {};
  };
  Material.prototype.getParameters = function() {
    return this.parameters;
  };
  Material.prototype.clearVariants = function() {
    var meshInstance;
    for (var s in this.variants) {
      if (this.variants.hasOwnProperty(s)) {
        this.variants[s]._refCount--;
      }
    }
    this.variants = {};
    var j;
    for (var i = 0;i < this.meshInstances.length;i++) {
      meshInstance = this.meshInstances[i];
      for (j = 0;j < meshInstance._shader.length;j++) {
        meshInstance._shader[j] = null;
      }
    }
  };
  Material.prototype.getParameter = function(name) {
    return this.parameters[name];
  };
  Material.prototype.setParameter = function(arg, data) {
    var name;
    if (data === undefined && typeof arg === "object") {
      var uniformObject = arg;
      if (uniformObject.length) {
        for (var i = 0;i < uniformObject.length;i++) {
          this.setParameter(uniformObject[i]);
        }
        return;
      } else {
        name = uniformObject.name;
        data = uniformObject.value;
      }
    } else {
      name = arg;
    }
    var param = this.parameters[name];
    if (param) {
      param.data = data;
    } else {
      this.parameters[name] = {scopeId:null, data:data};
    }
  };
  Material.prototype.deleteParameter = function(name) {
    if (this.parameters[name]) {
      delete this.parameters[name];
    }
  };
  Material.prototype.setParameters = function() {
    for (var paramName in this.parameters) {
      var parameter = this.parameters[paramName];
      parameter.scopeId.setValue(parameter.data);
    }
  };
  Material.prototype.update = function() {
    throw Error("Not Implemented in base class");
  };
  Material.prototype.init = function(data) {
    throw Error("Not Implemented in base class");
  };
  Material.prototype.getName = function() {
    return this.name;
  };
  Material.prototype.setName = function(name) {
    this.name = name;
  };
  Material.prototype.getShader = function() {
    return this.shader;
  };
  Material.prototype.setShader = function(shader) {
    if (this._shader) {
      this._shader._refCount--;
    }
    this._shader = shader;
    if (shader) {
      shader._refCount++;
    }
  };
  Material.prototype.destroy = function() {
    if (this.shader) {
      this.shader._refCount--;
      if (this.shader._refCount < 1) {
        this.shader.destroy();
      }
    }
    var variant;
    for (var s in this.variants) {
      if (this.variants.hasOwnProperty(s)) {
        variant = this.variants[s];
        if (variant === this.shader) {
          continue;
        }
        variant._refCount--;
        if (variant._refCount < 1) {
          variant.destroy();
        }
      }
    }
    this.variants = {};
    this.shader = null;
    var meshInstance, j;
    for (var i = 0;i < this.meshInstances.length;i++) {
      meshInstance = this.meshInstances[i];
      for (j = 0;j < meshInstance._shader.length;j++) {
        meshInstance._shader[j] = null;
      }
      meshInstance._material = null;
      if (this !== pc.Scene.defaultMaterial) {
        meshInstance.material = pc.Scene.defaultMaterial;
      }
    }
  };
  var StencilParameters = function(options) {
    this.func = options.func || pc.FUNC_ALWAYS;
    this.ref = options.ref || 0;
    this.mask = options.mask || 255;
    this.fail = options.fail || pc.STENCILOP_KEEP;
    this.zfail = options.zfail || pc.STENCILOP_KEEP;
    this.zpass = options.zpass || pc.STENCILOP_KEEP;
  };
  StencilParameters.prototype.clone = function() {
    var clone = new pc.StencilParameters({func:this.func, ref:this.ref, mask:this.mask, fail:this.fail, zfail:this.zfail, zpass:this.zpass});
    return clone;
  };
  return {Material:Material, StencilParameters:StencilParameters};
}());
pc.extend(pc, function() {
  var BasicMaterial = function() {
    this.color = new pc.Color(1, 1, 1, 1);
    this.colorMap = null;
    this.vertexColors = false;
    this.update();
  };
  BasicMaterial = pc.inherits(BasicMaterial, pc.Material);
  pc.extend(BasicMaterial.prototype, {clone:function() {
    var clone = new pc.BasicMaterial;
    pc.Material.prototype._cloneInternal.call(this, clone);
    clone.color.copy(this.color);
    clone.colorMap = this.colorMap;
    clone.vertexColors = this.vertexColors;
    clone.update();
    return clone;
  }, update:function() {
    this.clearParameters();
    this.setParameter("uColor", this.color.data);
    if (this.colorMap) {
      this.setParameter("texture_diffuseMap", this.colorMap);
    }
  }, updateShader:function(device) {
    var options = {skin:!!this.meshInstances[0].skinInstance, vertexColors:this.vertexColors, diffuseMap:this.colorMap};
    var library = device.getProgramLibrary();
    this.shader = library.getProgram("basic", options);
  }});
  return {BasicMaterial:BasicMaterial};
}());
pc.extend(pc, function() {
  var DepthMaterial = function() {
  };
  DepthMaterial = pc.inherits(DepthMaterial, pc.Material);
  pc.extend(DepthMaterial.prototype, {clone:function() {
    var clone = new pc.DepthMaterial;
    pc.Material.prototype._cloneInternal.call(this, clone);
    clone.update();
    return clone;
  }, update:function() {
  }, updateShader:function(device) {
    var options = {skin:!!this.meshInstances[0].skinInstance};
    var library = device.getProgramLibrary();
    this.shader = library.getProgram("depth", options);
  }});
  return {DepthMaterial:DepthMaterial};
}());
pc.extend(pc, function() {
  var _tempTiling = new pc.Vec3;
  var _tempOffset = new pc.Vec3;
  var lightBounds = new pc.BoundingBox;
  var tempSphere = {center:null, radius:0};
  var StandardMaterial = function() {
    this.reset();
    this.update();
  };
  var _createTexture = function(param) {
    if (param.data) {
      if (param.data instanceof pc.Texture) {
        return param.data;
      } else {
        return null;
      }
    } else {
      return null;
    }
  };
  var _createCubemap = function(param) {
    if (param.data) {
      if (param.data instanceof pc.Texture) {
        return param.data;
      }
    }
    return null;
  };
  var _createVec2 = function(param) {
    return new pc.Vec2(param.data[0], param.data[1]);
  };
  var _createVec3 = function(param) {
    return new pc.Vec3(param.data[0], param.data[1], param.data[2]);
  };
  var _createBoundingBox = function(param) {
    var center, halfExtents;
    if (param.data && param.data.center) {
      center = new pc.Vec3(param.data.center[0], param.data.center[1], param.data.center[2]);
    } else {
      center = new pc.Vec3(0, 0, 0);
    }
    if (param.data && param.data.halfExtents) {
      halfExtents = new pc.Vec3(param.data.halfExtents[0], param.data.halfExtents[1], param.data.halfExtents[2]);
    } else {
      halfExtents = new pc.Vec3(.5, .5, .5);
    }
    return new pc.BoundingBox(center, halfExtents);
  };
  var _createRgb = function(param) {
    return new pc.Color(param.data[0], param.data[1], param.data[2]);
  };
  var _propsSerial = [];
  var _propsSerialDefaultVal = [];
  var _propsInternalNull = [];
  var _propsInternalVec3 = [];
  var _prop2Uniform = {};
  var _defineTex2D = function(obj, name, uv, channels) {
    var privMap = "_" + name + "Map";
    var privMapTiling = privMap + "Tiling";
    var privMapOffset = privMap + "Offset";
    var mapTransform = privMap.substring(1) + "Transform";
    var privMapUv = privMap + "Uv";
    var privMapChannel = privMap + "Channel";
    var privMapVertexColor = privMap + "VertexColor";
    obj[privMap] = null;
    obj[privMapTiling] = new pc.Vec2(1, 1);
    obj[privMapOffset] = new pc.Vec2(0, 0);
    obj[mapTransform] = null;
    obj[privMapUv] = uv;
    if (channels > 0) {
      obj[privMapChannel] = channels > 1 ? "rgb" : "g";
    }
    obj[privMapVertexColor] = false;
    if (!pc._matTex2D) {
      pc._matTex2D = [];
    }
    pc._matTex2D[name] = channels;
    Object.defineProperty(StandardMaterial.prototype, privMap.substring(1), {get:function() {
      return this[privMap];
    }, set:function(value) {
      var oldVal = this[privMap];
      if (!oldVal && value || oldVal && !value) {
        this.dirtyShader = true;
      }
      if (oldVal && value) {
        if (oldVal.rgbm !== value.rgbm || oldVal.fixCubemapSeams !== value.fixCubemapSeams || oldVal.format !== value.format) {
          this.dirtyShader = true;
        }
      }
      this[privMap] = value;
    }});
    var mapTiling = privMapTiling.substring(1);
    var mapOffset = privMapOffset.substring(1);
    Object.defineProperty(StandardMaterial.prototype, mapTiling, {get:function() {
      this.dirtyShader = true;
      return this[privMapTiling];
    }, set:function(value) {
      this.dirtyShader = true;
      this[privMapTiling] = value;
    }});
    _prop2Uniform[mapTiling] = function(mat, val, changeMat) {
      var tform = mat._updateMapTransform(changeMat ? mat[mapTransform] : null, val, mat[privMapOffset]);
      return {name:"texture_" + mapTransform, value:tform.data};
    };
    Object.defineProperty(StandardMaterial.prototype, mapOffset, {get:function() {
      this.dirtyShader = true;
      return this[privMapOffset];
    }, set:function(value) {
      this.dirtyShader = true;
      this[privMapOffset] = value;
    }});
    _prop2Uniform[mapOffset] = function(mat, val, changeMat) {
      var tform = mat._updateMapTransform(changeMat ? mat[mapTransform] : null, mat[privMapTiling], val);
      return {name:"texture_" + mapTransform, value:tform.data};
    };
    Object.defineProperty(StandardMaterial.prototype, privMapUv.substring(1), {get:function() {
      return this[privMapUv];
    }, set:function(value) {
      this.dirtyShader = true;
      this[privMapUv] = value;
    }});
    Object.defineProperty(StandardMaterial.prototype, privMapChannel.substring(1), {get:function() {
      return this[privMapChannel];
    }, set:function(value) {
      this.dirtyShader = true;
      this[privMapChannel] = value;
    }});
    Object.defineProperty(StandardMaterial.prototype, privMapVertexColor.substring(1), {get:function() {
      return this[privMapVertexColor];
    }, set:function(value) {
      this.dirtyShader = true;
      this[privMapVertexColor] = value;
    }});
    _propsSerial.push(privMap.substring(1));
    _propsSerial.push(privMapTiling.substring(1));
    _propsSerial.push(privMapOffset.substring(1));
    _propsSerial.push(privMapUv.substring(1));
    _propsSerial.push(privMapChannel.substring(1));
    _propsSerial.push(privMapVertexColor.substring(1));
    _propsInternalNull.push(mapTransform);
  };
  var _propsColor = [];
  var _defineColor = function(obj, name, defaultValue, hasMultiplier) {
    var priv = "_" + name;
    var uform = name + "Uniform";
    var mult = name + "Intensity";
    var pmult = "_" + mult;
    obj[priv] = defaultValue;
    obj[uform] = new Float32Array(3);
    Object.defineProperty(StandardMaterial.prototype, name, {get:function() {
      this.dirtyColor = true;
      this.dirtyShader = true;
      return this[priv];
    }, set:function(value) {
      var oldVal = this[priv];
      var wasBw = oldVal.data[0] === 0 && oldVal.data[1] === 0 && oldVal.data[2] === 0 || oldVal.data[0] === 1 && oldVal.data[1] === 1 && oldVal.data[2] === 1;
      var isBw = value.data[0] === 0 && value.data[1] === 0 && value.data[2] === 0 || value.data[0] === 1 && value.data[1] === 1 && value.data[2] === 1;
      if (wasBw || isBw) {
        this.dirtyShader = true;
      }
      this.dirtyColor = true;
      this[priv] = value;
    }});
    _propsSerial.push(name);
    _propsInternalVec3.push(uform);
    _propsColor.push(name);
    _prop2Uniform[name] = function(mat, val, changeMat) {
      var arr = changeMat ? mat[uform] : new Float32Array(3);
      var gammaCorrection = false;
      if (mat.useGammaTonemap) {
        var scene = mat._scene || pc.Application.getApplication().scene;
        gammaCorrection = scene.gammaCorrection;
      }
      for (var c = 0;c < 3;c++) {
        if (gammaCorrection) {
          arr[c] = Math.pow(val.data[c], 2.2);
        } else {
          arr[c] = val.data[c];
        }
        if (hasMultiplier) {
          arr[c] *= mat[pmult];
        }
      }
      return {name:"material_" + name, value:arr};
    };
    if (hasMultiplier) {
      obj[pmult] = 1;
      Object.defineProperty(StandardMaterial.prototype, mult, {get:function() {
        return this[pmult];
      }, set:function(value) {
        var oldVal = this[pmult];
        var wasBw = oldVal === 0 || oldVal === 1;
        var isBw = value === 0 || value === 1;
        if (wasBw || isBw) {
          this.dirtyShader = true;
        }
        this.dirtyColor = true;
        this[pmult] = value;
      }});
      _propsSerial.push(mult);
      _prop2Uniform[mult] = function(mat, val, changeMat) {
        var arr = changeMat ? mat[uform] : new Float32Array(3);
        var gammaCorrection = false;
        if (mat.useGammaTonemap) {
          var scene = mat._scene || pc.Application.getApplication().scene;
          gammaCorrection = scene.gammaCorrection;
        }
        for (var c = 0;c < 3;c++) {
          if (gammaCorrection) {
            arr[c] = Math.pow(mat[priv].data[c], 2.2);
          } else {
            arr[c] = mat[priv].data[c];
          }
          arr[c] *= mat[pmult];
        }
        return {name:"material_" + name, value:arr};
      };
    }
  };
  var _defineFloat = function(obj, name, defaultValue, func) {
    var priv = "_" + name;
    obj[priv] = defaultValue;
    Object.defineProperty(StandardMaterial.prototype, name, {get:function() {
      return this[priv];
    }, set:function(value) {
      var oldVal = this[priv];
      var wasBw = oldVal === 0 || oldVal === 1;
      var isBw = value === 0 || value === 1;
      if (wasBw || isBw) {
        this.dirtyShader = true;
      }
      this[priv] = value;
    }});
    _propsSerial.push(name);
    _prop2Uniform[name] = func !== undefined ? func : function(mat, val, changeMat) {
      return {name:"material_" + name, value:val};
    };
  };
  var _defineObject = function(obj, name, func) {
    var priv = "_" + name;
    obj[priv] = null;
    Object.defineProperty(StandardMaterial.prototype, name, {get:function() {
      return this[priv];
    }, set:function(value) {
      var oldVal = this[priv];
      if (!oldVal && value || oldVal && !value) {
        this.dirtyShader = true;
      }
      this[priv] = value;
    }});
    _propsSerial.push(name);
    _prop2Uniform[name] = func;
  };
  var _defineChunks = function(obj) {
    this._chunks = null;
    Object.defineProperty(StandardMaterial.prototype, "chunks", {get:function() {
      this.dirtyShader = true;
      return this._chunks;
    }, set:function(value) {
      this.dirtyShader = true;
      this._chunks = value;
    }});
    _propsSerial.push("chunks");
  };
  var _defineFlag = function(obj, name, defaultValue) {
    var priv = "_" + name;
    obj[priv] = defaultValue;
    Object.defineProperty(StandardMaterial.prototype, name, {get:function() {
      return this[priv];
    }, set:function(value) {
      this.dirtyShader = true;
      this[priv] = value;
    }});
    _propsSerial.push(name);
  };
  var Chunks = function() {
  };
  Chunks.prototype.copy = function(from) {
    for (var p in from) {
      if (from.hasOwnProperty(p) && p !== "copy") {
        this[p] = from[p];
      }
    }
  };
  StandardMaterial = pc.inherits(StandardMaterial, pc.Material);
  pc.extend(StandardMaterial.prototype, {reset:function() {
    this.blendType = pc.BLEND_NONE;
    var i;
    for (i = 0;i < _propsSerial.length;i++) {
      var defVal = _propsSerialDefaultVal[i];
      this[_propsSerial[i]] = defVal ? defVal.clone ? defVal.clone() : defVal : defVal;
    }
    for (i = 0;i < _propsInternalNull.length;i++) {
      this[_propsInternalNull[i]] = null;
    }
    for (i = 0;i < _propsInternalVec3.length;i++) {
      this[_propsInternalVec3[i]] = new Float32Array(3);
    }
    this._chunks = new Chunks;
    this.cubeMapMinUniform = new Float32Array(3);
    this.cubeMapMaxUniform = new Float32Array(3);
  }, clone:function() {
    var clone = new pc.StandardMaterial;
    pc.Material.prototype._cloneInternal.call(this, clone);
    var pname;
    for (var i = 0;i < _propsSerial.length;i++) {
      pname = _propsSerial[i];
      if (this[pname] !== undefined) {
        if (this[pname] && this[pname].copy) {
          if (clone[pname]) {
            clone[pname].copy(this[pname]);
          } else {
            clone[pname] = this[pname].clone();
          }
        } else {
          clone[pname] = this[pname];
        }
      }
    }
    if (!clone.shader) {
      clone.update();
    }
    return clone;
  }, init:function(data) {
    this.reset();
    this.name = data.name;
    if (data.chunks) {
      this.chunks.copy(data.chunks);
    }
    for (var i = 0;i < data.parameters.length;i++) {
      var param = data.parameters[i];
      if (param.type === "vec3") {
        this[param.name] = _createRgb(param);
      } else {
        if (param.type === "vec2") {
          this[param.name] = _createVec2(param);
        } else {
          if (param.type === "texture") {
            this[param.name] = _createTexture(param);
          } else {
            if (param.type === "cubemap") {
              this[param.name] = _createCubemap(param);
            } else {
              if (param.name === "bumpMapFactor") {
                this.bumpiness = param.data;
              } else {
                if (param.type === "boundingbox") {
                  this[param.name] = _createBoundingBox(param);
                } else {
                  this[param.name] = param.data;
                }
              }
            }
          }
        }
      }
    }
    this.update();
  }, _updateMapTransform:function(transform, tiling, offset) {
    transform = transform || new pc.Vec4;
    transform.set(tiling.x, tiling.y, offset.x, offset.y);
    if (transform.x === 1 && transform.y === 1 && transform.z === 0 && transform.w === 0) {
      return null;
    }
    return transform;
  }, _collectLights:function(lType, lights, lightsSorted, mask, staticLightList) {
    var light;
    var i;
    for (i = 0;i < lights.length;i++) {
      light = lights[i];
      if (light._enabled) {
        if (light._mask & mask) {
          if (light._type === lType) {
            if (lType !== pc.LIGHTTYPE_DIRECTIONAL) {
              if (light.isStatic) {
                continue;
              }
            }
            lightsSorted.push(light);
          }
        }
      }
    }
    if (staticLightList) {
      for (i = 0;i < staticLightList.length;i++) {
        light = staticLightList[i];
        if (light._type === lType) {
          lightsSorted.push(light);
        }
      }
    }
  }, _setParameter:function(name, value) {
    this.setParameter(name, value);
    this._propsSet.push(name);
  }, _clearParameters:function() {
    var props = this._propsSet;
    for (var i = 0;i < props.length;i++) {
      delete this.parameters[props[i]];
    }
    this._propsSet = [];
  }, _updateMap:function(p) {
    var mname = p + "Map";
    if (this[mname]) {
      this._setParameter("texture_" + mname, this[mname]);
      var tname = mname + "Transform";
      this[tname] = this._updateMapTransform(this[tname], this[mname + "Tiling"], this[mname + "Offset"]);
      if (this[tname]) {
        this._setParameter("texture_" + tname, this[tname].data);
      }
    }
  }, getUniform:function(varName, value, changeMat) {
    var func = _prop2Uniform[varName];
    if (func) {
      return func(this, value, changeMat);
    }
    return null;
  }, update:function() {
    this._clearParameters();
    this._setParameter("material_ambient", this.ambientUniform);
    if (!this.diffuseMap || this.diffuseMapTint) {
      this._setParameter("material_diffuse", this.diffuseUniform);
    }
    if (!this.useMetalness) {
      if (!this.specularMap || this.specularMapTint) {
        this._setParameter("material_specular", this.specularUniform);
      }
    } else {
      if (!this.metalnessMap || this.metalness < 1) {
        this._setParameter("material_metalness", this.metalness);
      }
    }
    this._setParameter(this.getUniform("shininess", this.shininess, true));
    if (!this.emissiveMap || this.emissiveMapTint) {
      this._setParameter("material_emissive", this.emissiveUniform);
    }
    if (this.emissiveMap) {
      this._setParameter("material_emissiveIntensity", this.emissiveIntensity);
    }
    if (this.refraction > 0) {
      this._setParameter("material_refraction", this.refraction);
      this._setParameter("material_refractionIndex", this.refractionIndex);
    }
    this._setParameter("material_opacity", this.opacity);
    if (this.occludeSpecular) {
      this._setParameter("material_occludeSpecularIntensity", this.occludeSpecularIntensity);
    }
    if (this.cubeMapProjection === pc.CUBEPROJ_BOX) {
      this._setParameter(this.getUniform("cubeMapProjectionBox", this.cubeMapProjectionBox, true));
    }
    for (var p in pc._matTex2D) {
      this._updateMap(p);
    }
    if (this.ambientSH) {
      this._setParameter("ambientSH[0]", this.ambientSH);
    }
    if (this.normalMap) {
      this._setParameter("material_bumpiness", this.bumpiness);
    }
    if (this.heightMap) {
      this._setParameter(this.getUniform("heightMapFactor", this.heightMapFactor, true));
    }
    if (this.cubeMap) {
      this._setParameter("texture_cubeMap", this.cubeMap);
    }
    if (this.prefilteredCubeMap128) {
      this._setParameter("texture_prefilteredCubeMap128", this.prefilteredCubeMap128);
    }
    if (this.prefilteredCubeMap64) {
      this._setParameter("texture_prefilteredCubeMap64", this.prefilteredCubeMap64);
    }
    if (this.prefilteredCubeMap32) {
      this._setParameter("texture_prefilteredCubeMap32", this.prefilteredCubeMap32);
    }
    if (this.prefilteredCubeMap16) {
      this._setParameter("texture_prefilteredCubeMap16", this.prefilteredCubeMap16);
    }
    if (this.prefilteredCubeMap8) {
      this._setParameter("texture_prefilteredCubeMap8", this.prefilteredCubeMap8);
    }
    if (this.prefilteredCubeMap4) {
      this._setParameter("texture_prefilteredCubeMap4", this.prefilteredCubeMap4);
    }
    if (this.sphereMap) {
      this._setParameter("texture_sphereMap", this.sphereMap);
    }
    if (this.dpAtlas) {
      this._setParameter("texture_sphereMap", this.dpAtlas);
    }
    this._setParameter("material_reflectivity", this.reflectivity);
    if (this.dirtyShader || !this._scene) {
      this.shader = null;
      this.clearVariants();
    }
    this._processColor();
  }, _processColor:function() {
    if (!this.dirtyColor) {
      return;
    }
    if (!this._scene && this.useGammaTonemap) {
      return;
    }
    var gammaCorrection = false;
    if (this.useGammaTonemap) {
      gammaCorrection = this._scene.gammaCorrection;
    }
    for (var i = 0;i < _propsColor.length;i++) {
      var clr = this["_" + _propsColor[i]];
      var arr = this[_propsColor[i] + "Uniform"];
      for (var c = 0;c < 3;c++) {
        if (gammaCorrection) {
          arr[c] = Math.pow(clr.data[c], 2.2);
        } else {
          arr[c] = clr.data[c];
        }
      }
    }
    for (c = 0;c < 3;c++) {
      this.emissiveUniform[c] *= this.emissiveIntensity;
    }
    this.dirtyColor = false;
  }, _getMapTransformID:function(xform, uv) {
    if (!xform) {
      return 0;
    }
    if (!this._mapXForms[uv]) {
      this._mapXForms[uv] = [];
    }
    var i, j, same;
    for (i = 0;i < this._mapXForms[uv].length;i++) {
      same = true;
      for (j = 0;j < xform.data.length;j++) {
        if (this._mapXForms[uv][i][j] != xform.data[j]) {
          same = false;
          break;
        }
      }
      if (same) {
        return i + 1;
      }
    }
    var newID = this._mapXForms[uv].length;
    this._mapXForms[uv][newID] = [];
    for (j = 0;j < xform.data.length;j++) {
      this._mapXForms[uv][newID][j] = xform.data[j];
    }
    return newID + 1;
  }, updateShader:function(device, scene, objDefs, staticLightList) {
    var i, c;
    if (!this._scene) {
      this._scene = scene;
      this._processColor();
    }
    var lights = scene._lights;
    this._mapXForms = [];
    var useTexCubeLod = device.useTexCubeLod;
    var useDp = !device.extTextureLod;
    var globalSky128, globalSky64, globalSky32, globalSky16, globalSky8, globalSky4;
    if (this.useSkybox) {
      globalSky128 = scene._skyboxPrefiltered[0];
      globalSky64 = scene._skyboxPrefiltered[1];
      globalSky32 = scene._skyboxPrefiltered[2];
      globalSky16 = scene._skyboxPrefiltered[3];
      globalSky8 = scene._skyboxPrefiltered[4];
      globalSky4 = scene._skyboxPrefiltered[5];
    }
    var prefilteredCubeMap128 = this.prefilteredCubeMap128 || globalSky128;
    var prefilteredCubeMap64 = this.prefilteredCubeMap64 || globalSky64;
    var prefilteredCubeMap32 = this.prefilteredCubeMap32 || globalSky32;
    var prefilteredCubeMap16 = this.prefilteredCubeMap16 || globalSky16;
    var prefilteredCubeMap8 = this.prefilteredCubeMap8 || globalSky8;
    var prefilteredCubeMap4 = this.prefilteredCubeMap4 || globalSky4;
    if (prefilteredCubeMap128) {
      var allMips = prefilteredCubeMap128 && prefilteredCubeMap64 && prefilteredCubeMap32 && prefilteredCubeMap16 && prefilteredCubeMap8 && prefilteredCubeMap4;
      if (useDp && allMips) {
        if (!prefilteredCubeMap128.dpAtlas) {
          prefilteredCubeMap128.dpAtlas = pc.generateDpAtlas(device, [prefilteredCubeMap128, prefilteredCubeMap64, prefilteredCubeMap32, prefilteredCubeMap16, prefilteredCubeMap8, prefilteredCubeMap4]);
          prefilteredCubeMap128.sh = pc.shFromCubemap(prefilteredCubeMap16);
        }
        this.dpAtlas = prefilteredCubeMap128.dpAtlas;
        this.ambientSH = prefilteredCubeMap128.sh;
        this._setParameter("ambientSH[0]", this.ambientSH);
        this._setParameter("texture_sphereMap", this.dpAtlas);
      } else {
        if (useTexCubeLod) {
          if (prefilteredCubeMap128._levels.length < 6) {
            if (allMips) {
              this._setParameter("texture_prefilteredCubeMap128", prefilteredCubeMap128);
            } else {
              console.log("Can't use prefiltered cubemap: " + allMips + ", " + useTexCubeLod + ", " + prefilteredCubeMap128._levels);
            }
          } else {
            this._setParameter("texture_prefilteredCubeMap128", prefilteredCubeMap128);
          }
        } else {
          if (allMips) {
            this._setParameter("texture_prefilteredCubeMap128", prefilteredCubeMap128);
            this._setParameter("texture_prefilteredCubeMap64", prefilteredCubeMap64);
            this._setParameter("texture_prefilteredCubeMap32", prefilteredCubeMap32);
            this._setParameter("texture_prefilteredCubeMap16", prefilteredCubeMap16);
            this._setParameter("texture_prefilteredCubeMap8", prefilteredCubeMap8);
            this._setParameter("texture_prefilteredCubeMap4", prefilteredCubeMap4);
          } else {
            console.log("Can't use prefiltered cubemap: " + allMips + ", " + useTexCubeLod + ", " + prefilteredCubeMap128._levels);
          }
        }
      }
    }
    var specularTint = false;
    var useSpecular = (this.useMetalness ? true : !!this.specularMap) || !!this.sphereMap || !!this.cubeMap || !!this.dpAtlas;
    useSpecular = useSpecular || (this.useMetalness ? true : !(this.specular.data[0] === 0 && this.specular.data[1] === 0 && this.specular.data[2] === 0));
    if (useSpecular) {
      if (this.specularMapTint && !this.useMetalness) {
        specularTint = this.specular.data[0] !== 1 || this.specular.data[1] !== 1 || this.specular.data[2] !== 1;
      }
    }
    var rgbmReflection = (prefilteredCubeMap128 ? prefilteredCubeMap128.rgbm : false) || (this.cubeMap ? this.cubeMap.rgbm : false) || (this.sphereMap ? this.sphereMap.rgbm : false) || (this.dpAtlas ? this.dpAtlas.rgbm : false);
    var hdrReflection = (prefilteredCubeMap128 ? prefilteredCubeMap128.rgbm || prefilteredCubeMap128.format === pc.PIXELFORMAT_RGBA32F : false) || (this.cubeMap ? this.cubeMap.rgbm || this.cubeMap.format === pc.PIXELFORMAT_RGBA32F : false) || (this.sphereMap ? this.sphereMap.rgbm || this.sphereMap.format === pc.PIXELFORMAT_RGBA32F : false) || (this.dpAtlas ? this.dpAtlas.rgbm || this.dpAtlas.format === pc.PIXELFORMAT_RGBA32F : false);
    var emissiveTint = (this.emissive.data[0] !== 1 || this.emissive.data[1] !== 1 || this.emissive.data[2] !== 1 || this.emissiveIntensity !== 1) && this.emissiveMapTint;
    emissiveTint = emissiveTint ? 3 : this.emissiveIntensity !== 1 ? 1 : 0;
    var options = {fog:this.useFog ? scene.fog : "none", gamma:this.useGammaTonemap ? scene.gammaCorrection : pc.GAMMA_NONE, toneMap:this.useGammaTonemap ? scene.toneMapping : -1, blendMapsWithColors:true, modulateAmbient:this.ambientTint, diffuseTint:(this.diffuse.data[0] !== 1 || this.diffuse.data[1] !== 1 || this.diffuse.data[2] !== 1) && this.diffuseMapTint, specularTint:specularTint, metalnessTint:this.useMetalness && this.metalness < 1, glossTint:true, emissiveTint:emissiveTint, opacityTint:this.opacity !== 
    1 && this.blendType !== pc.BLEND_NONE, alphaTest:this.alphaTest > 0, needsNormalFloat:this.normalizeNormalMap, sphereMap:!!this.sphereMap, cubeMap:!!this.cubeMap, dpAtlas:!!this.dpAtlas, ambientSH:!!this.ambientSH, useSpecular:useSpecular, rgbmReflection:rgbmReflection, hdrReflection:hdrReflection, fixSeams:prefilteredCubeMap128 ? prefilteredCubeMap128.fixCubemapSeams : this.cubeMap ? this.cubeMap.fixCubemapSeams : false, prefilteredCubemap:!!prefilteredCubeMap128, emissiveFormat:this.emissiveMap ? 
    this.emissiveMap.rgbm ? 1 : this.emissiveMap.format === pc.PIXELFORMAT_RGBA32F ? 2 : 0 : null, lightMapFormat:this.lightMap ? this.lightMap.rgbm ? 1 : this.lightMap.format === pc.PIXELFORMAT_RGBA32F ? 2 : 0 : null, useRgbm:rgbmReflection || (this.emissiveMap ? this.emissiveMap.rgbm : 0) || (this.lightMap ? this.lightMap.rgbm : 0), specularAA:this.specularAntialias, conserveEnergy:this.conserveEnergy, occludeSpecular:this.occludeSpecular, occludeSpecularFloat:this.occludeSpecularIntensity !== 
    1, occludeDirect:this.occludeDirect, shadingModel:this.shadingModel, fresnelModel:this.fresnelModel, packedNormal:this.normalMap ? this.normalMap.format === pc.PIXELFORMAT_DXT5 : false, shadowSampleType:this.shadowSampleType, forceFragmentPrecision:this.forceFragmentPrecision, fastTbn:this.fastTbn, cubeMapProjection:this.cubeMapProjection, chunks:this.chunks, customFragmentShader:this.customFragmentShader, refraction:!!this.refraction, useMetalness:this.useMetalness, blendType:this.blendType, 
    skyboxIntensity:prefilteredCubeMap128 === globalSky128 && prefilteredCubeMap128 && scene.skyboxIntensity !== 1, forceUv1:this.forceUv1, useTexCubeLod:useTexCubeLod, msdf:!!this.msdfMap};
    var hasUv0 = false;
    var hasUv1 = false;
    var hasVcolor = false;
    if (objDefs) {
      options.noShadow = (objDefs & pc.SHADERDEF_NOSHADOW) !== 0;
      options.screenSpace = (objDefs & pc.SHADERDEF_SCREENSPACE) !== 0;
      options.skin = (objDefs & pc.SHADERDEF_SKIN) !== 0;
      options.useInstancing = (objDefs & pc.SHADERDEF_INSTANCING) !== 0;
      if ((objDefs & pc.SHADERDEF_LM) !== 0) {
        options.lightMapFormat = 1;
        options.lightMap = true;
        options.lightMapChannel = "rgb";
        options.lightMapUv = 1;
        options.lightMapTransform = 0;
        options.lightMapWithoutAmbient = true;
        options.useRgbm = true;
        if ((objDefs & pc.SHADERDEF_DIRLM) !== 0) {
          options.dirLightMap = true;
        }
      }
      hasUv0 = (objDefs & pc.SHADERDEF_UV0) !== 0;
      hasUv1 = (objDefs & pc.SHADERDEF_UV1) !== 0;
      hasVcolor = (objDefs & pc.SHADERDEF_VCOLOR) !== 0;
    }
    for (var p in pc._matTex2D) {
      if (p === "opacity" && this.blendType === pc.BLEND_NONE && this.alphaTest === 0) {
        continue;
      }
      var cname;
      var mname = p + "Map";
      var vname = mname + "VertexColor";
      if (p !== "height" && this[vname]) {
        if (hasVcolor) {
          cname = mname + "Channel";
          options[vname] = this[vname];
          options[cname] = this[cname];
          options.vertexColors = true;
        }
      } else {
        if (this[mname]) {
          var uname = mname + "Uv";
          var allow = true;
          if (this[uname] === 0 && !hasUv0) {
            allow = false;
          }
          if (this[uname] === 1 && !hasUv1) {
            allow = false;
          }
          if (allow) {
            options[mname] = !!this[mname];
            var tname = mname + "Transform";
            cname = mname + "Channel";
            options[tname] = this._getMapTransformID(this[tname], this[uname]);
            options[cname] = this[cname];
            options[uname] = this[uname];
          }
        }
      }
    }
    options.aoMapUv = options.aoMapUv || this.aoUvSet;
    this._mapXForms = null;
    if (this.useLighting) {
      var lightsSorted = [];
      var mask = objDefs ? objDefs >> 16 : 1;
      this._collectLights(pc.LIGHTTYPE_DIRECTIONAL, lights, lightsSorted, mask);
      this._collectLights(pc.LIGHTTYPE_POINT, lights, lightsSorted, mask, staticLightList);
      this._collectLights(pc.LIGHTTYPE_SPOT, lights, lightsSorted, mask, staticLightList);
      options.lights = lightsSorted;
    } else {
      options.lights = [];
    }
    var library = device.getProgramLibrary();
    this.shader = library.getProgram("standard", options);
    if (!objDefs) {
      this.clearVariants();
      this.variants[0] = this.shader;
    }
    this.dirtyShader = false;
  }});
  var _defineMaterialProps = function(obj) {
    obj.dirtyShader = true;
    obj.dirtyColor = true;
    obj._scene = null;
    _defineColor(obj, "ambient", new pc.Color(.7, .7, .7));
    _defineColor(obj, "diffuse", new pc.Color(1, 1, 1));
    _defineColor(obj, "specular", new pc.Color(0, 0, 0));
    _defineColor(obj, "emissive", new pc.Color(0, 0, 0), true);
    _defineFloat(obj, "shininess", 25, function(mat, shininess) {
      var value;
      if (mat.shadingModel === pc.SPECULAR_PHONG) {
        value = Math.pow(2, shininess * .01 * 11);
      } else {
        value = shininess * .01;
      }
      return {name:"material_shininess", value:value};
    });
    _defineFloat(obj, "heightMapFactor", 1, function(mat, height) {
      return {name:"material_heightMapFactor", value:height * .025};
    });
    _defineFloat(obj, "opacity", 1);
    _defineFloat(obj, "alphaTest", 0);
    _defineFloat(obj, "bumpiness", 1);
    _defineFloat(obj, "reflectivity", 1);
    _defineFloat(obj, "occludeSpecularIntensity", 1);
    _defineFloat(obj, "refraction", 0);
    _defineFloat(obj, "refractionIndex", 1 / 1.5);
    _defineFloat(obj, "metalness", 1);
    _defineFloat(obj, "aoUvSet", 0, null);
    _defineObject(obj, "ambientSH", function(mat, val, changeMat) {
      return {name:"ambientSH[0]", value:val};
    });
    _defineObject(obj, "cubeMapProjectionBox", function(mat, val, changeMat) {
      var bmin = changeMat ? mat.cubeMapMinUniform : new Float32Array(3);
      var bmax = changeMat ? mat.cubeMapMaxUniform : new Float32Array(3);
      bmin[0] = val.center.x - val.halfExtents.x;
      bmin[1] = val.center.y - val.halfExtents.y;
      bmin[2] = val.center.z - val.halfExtents.z;
      bmax[0] = val.center.x + val.halfExtents.x;
      bmax[1] = val.center.y + val.halfExtents.y;
      bmax[2] = val.center.z + val.halfExtents.z;
      return [{name:"envBoxMin", value:bmin}, {name:"envBoxMax", value:bmax}];
    });
    _defineChunks(obj);
    _defineFlag(obj, "ambientTint", false);
    _defineFlag(obj, "diffuseMapTint", false);
    _defineFlag(obj, "specularMapTint", false);
    _defineFlag(obj, "emissiveMapTint", false);
    _defineFlag(obj, "fastTbn", false);
    _defineFlag(obj, "specularAntialias", false);
    _defineFlag(obj, "useMetalness", false);
    _defineFlag(obj, "occludeDirect", false);
    _defineFlag(obj, "normalizeNormalMap", true);
    _defineFlag(obj, "conserveEnergy", true);
    _defineFlag(obj, "occludeSpecular", pc.SPECOCC_AO);
    _defineFlag(obj, "shadingModel", pc.SPECULAR_BLINN);
    _defineFlag(obj, "fresnelModel", pc.FRESNEL_NONE);
    _defineFlag(obj, "cubeMapProjection", pc.CUBEPROJ_NONE);
    _defineFlag(obj, "shadowSampleType", pc.SHADOWSAMPLE_PCF3X3);
    _defineFlag(obj, "customFragmentShader", null);
    _defineFlag(obj, "forceFragmentPrecision", null);
    _defineFlag(obj, "useFog", true);
    _defineFlag(obj, "useLighting", true);
    _defineFlag(obj, "useGammaTonemap", true);
    _defineFlag(obj, "useSkybox", true);
    _defineFlag(obj, "forceUv1", false);
    _defineTex2D(obj, "diffuse", 0, 3);
    _defineTex2D(obj, "specular", 0, 3);
    _defineTex2D(obj, "emissive", 0, 3);
    _defineTex2D(obj, "normal", 0, -1);
    _defineTex2D(obj, "metalness", 0, 1);
    _defineTex2D(obj, "gloss", 0, 1);
    _defineTex2D(obj, "opacity", 0, 1);
    _defineTex2D(obj, "height", 0, 1);
    _defineTex2D(obj, "ao", 0, 1);
    _defineTex2D(obj, "light", 1, 3);
    _defineTex2D(obj, "msdf", 0, 3);
    _defineObject(obj, "cubeMap");
    _defineObject(obj, "sphereMap");
    _defineObject(obj, "dpAtlas");
    _defineObject(obj, "prefilteredCubeMap128");
    _defineObject(obj, "prefilteredCubeMap64");
    _defineObject(obj, "prefilteredCubeMap32");
    _defineObject(obj, "prefilteredCubeMap16");
    _defineObject(obj, "prefilteredCubeMap8");
    _defineObject(obj, "prefilteredCubeMap4");
    for (var i = 0;i < _propsSerial.length;i++) {
      _propsSerialDefaultVal[i] = obj[_propsSerial[i]];
    }
    obj._propsSet = [];
  };
  _defineMaterialProps(StandardMaterial.prototype);
  return {StandardMaterial:StandardMaterial};
}());
pc.extend(pc, function() {
  var id = 0;
  var Mesh = function() {
    this._refCount = 0;
    this.id = id++;
    this.vertexBuffer = null;
    this.indexBuffer = [null];
    this.primitive = [{type:0, base:0, count:0}];
    this.skin = null;
    this.aabb = new pc.BoundingBox;
    this.boneAabb = null;
  };
  var MeshInstance = function MeshInstance(node, mesh, material) {
    this._key = [0, 0];
    this._shader = [null, null, null];
    this.isStatic = false;
    this._staticLightList = null;
    this._staticSource = null;
    this.node = node;
    this._mesh = mesh;
    mesh._refCount++;
    this.material = material;
    this._shaderDefs = 1 << 16;
    this._shaderDefs |= mesh.vertexBuffer.format.hasUv0 ? pc.SHADERDEF_UV0 : 0;
    this._shaderDefs |= mesh.vertexBuffer.format.hasUv1 ? pc.SHADERDEF_UV1 : 0;
    this._shaderDefs |= mesh.vertexBuffer.format.hasColor ? pc.SHADERDEF_VCOLOR : 0;
    this.visible = true;
    this.layer = pc.LAYER_WORLD;
    this.renderStyle = pc.RENDERSTYLE_SOLID;
    this.castShadow = false;
    this._receiveShadow = true;
    this._screenSpace = false;
    this.drawToDepth = true;
    this.cull = true;
    this.pick = true;
    this._updateAabb = true;
    this.updateKey();
    this._skinInstance = null;
    this.instancingData = null;
    this.aabb = new pc.BoundingBox;
    this.normalMatrix = new pc.Mat3;
    this._boneAabb = null;
    this._aabbVer = -1;
    this.parameters = {};
  };
  Object.defineProperty(MeshInstance.prototype, "mesh", {get:function() {
    return this._mesh;
  }, set:function(mesh) {
    if (this._mesh) {
      this._mesh._refCount--;
    }
    this._mesh = mesh;
    if (mesh) {
      mesh._refCount++;
    }
  }});
  Object.defineProperty(MeshInstance.prototype, "aabb", {get:function() {
    if (!this._updateAabb) {
      return this._aabb;
    }
    if (this.skinInstance) {
      var numBones = this.mesh.skin.boneNames.length;
      var i;
      if (!this.mesh.boneAabb) {
        this.mesh.boneAabb = [];
        this.mesh.boneUsed = [];
        var elems = this.mesh.vertexBuffer.format.elements;
        var numVerts = this.mesh.vertexBuffer.numVertices;
        var vertSize = this.mesh.vertexBuffer.format.size;
        var index;
        var offsetP, offsetI, offsetW;
        var j, k;
        for (i = 0;i < elems.length;i++) {
          if (elems[i].name === pc.SEMANTIC_POSITION) {
            offsetP = elems[i].offset;
          } else {
            if (elems[i].name === pc.SEMANTIC_BLENDINDICES) {
              offsetI = elems[i].offset;
            } else {
              if (elems[i].name === pc.SEMANTIC_BLENDWEIGHT) {
                offsetW = elems[i].offset;
              }
            }
          }
        }
        var data8 = new Uint8Array(this.mesh.vertexBuffer.storage);
        var dataF = new Float32Array(this.mesh.vertexBuffer.storage);
        var offsetPF = offsetP / 4;
        var offsetWF = offsetW / 4;
        var vertSizeF = vertSize / 4;
        var bMax, bMin;
        var x, y, z;
        var boneMin = [];
        var boneMax = [];
        var boneUsed = this.mesh.boneUsed;
        for (i = 0;i < numBones;i++) {
          boneMin[i] = new pc.Vec3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
          boneMax[i] = new pc.Vec3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
        }
        for (j = 0;j < numVerts;j++) {
          for (k = 0;k < 4;k++) {
            if (dataF[j * vertSizeF + offsetWF + k] > 0) {
              index = data8[j * vertSize + offsetI + k];
              x = dataF[j * vertSizeF + offsetPF];
              y = dataF[j * vertSizeF + offsetPF + 1];
              z = dataF[j * vertSizeF + offsetPF + 2];
              bMax = boneMax[index];
              bMin = boneMin[index];
              if (bMin.x > x) {
                bMin.x = x;
              }
              if (bMin.y > y) {
                bMin.y = y;
              }
              if (bMin.z > z) {
                bMin.z = z;
              }
              if (bMax.x < x) {
                bMax.x = x;
              }
              if (bMax.y < y) {
                bMax.y = y;
              }
              if (bMax.z < z) {
                bMax.z = z;
              }
              boneUsed[index] = true;
            }
          }
        }
        var aabb;
        for (i = 0;i < numBones;i++) {
          aabb = new pc.BoundingBox;
          aabb.setMinMax(boneMin[i], boneMax[i]);
          this.mesh.boneAabb.push(aabb);
        }
      }
      if (!this._boneAabb) {
        this._boneAabb = [];
        for (i = 0;i < this.mesh.boneAabb.length;i++) {
          this._boneAabb[i] = new pc.BoundingBox;
        }
      }
      var boneUsed = this.mesh.boneUsed;
      for (i = 0;i < this.mesh.boneAabb.length;i++) {
        if (!boneUsed[i]) {
          continue;
        }
        this._boneAabb[i].setFromTransformedAabb(this.mesh.boneAabb[i], this.skinInstance.matrices[i]);
        this._boneAabb[i].center.add(this.skinInstance.rootNode.getPosition());
      }
      var first = true;
      for (i = 0;i < this.mesh.boneAabb.length;i++) {
        if (!boneUsed[i]) {
          continue;
        }
        if (first) {
          this._aabb.center.copy(this._boneAabb[i].center);
          this._aabb.halfExtents.copy(this._boneAabb[i].halfExtents);
          first = false;
        } else {
          this._aabb.add(this._boneAabb[i]);
        }
      }
    } else {
      if (this.node._aabbVer !== this._aabbVer) {
        this._aabb.setFromTransformedAabb(this.mesh.aabb, this.node.getWorldTransform());
        this._aabbVer = this.node._aabbVer;
      }
    }
    return this._aabb;
  }, set:function(aabb) {
    this._aabb = aabb;
  }});
  Object.defineProperty(MeshInstance.prototype, "material", {get:function() {
    return this._material;
  }, set:function(material) {
    var i;
    for (i = 0;i < this._shader.length;i++) {
      this._shader[i] = null;
    }
    if (this._material) {
      var meshInstances = this._material.meshInstances;
      i = meshInstances.indexOf(this);
      if (i !== -1) {
        meshInstances.splice(i, 1);
      }
    }
    this._material = material;
    this._material.meshInstances.push(this);
    this.updateKey();
  }});
  Object.defineProperty(MeshInstance.prototype, "layer", {get:function() {
    return this._layer;
  }, set:function(layer) {
    this._layer = layer;
    this.updateKey();
  }});
  Object.defineProperty(MeshInstance.prototype, "receiveShadow", {get:function() {
    return this._receiveShadow;
  }, set:function(val) {
    this._receiveShadow = val;
    this._shaderDefs = val ? this._shaderDefs & ~pc.SHADERDEF_NOSHADOW : this._shaderDefs | pc.SHADERDEF_NOSHADOW;
    this._shader[pc.SHADER_FORWARD] = null;
  }});
  Object.defineProperty(MeshInstance.prototype, "skinInstance", {get:function() {
    return this._skinInstance;
  }, set:function(val) {
    this._skinInstance = val;
    this._shaderDefs = val ? this._shaderDefs | pc.SHADERDEF_SKIN : this._shaderDefs & ~pc.SHADERDEF_SKIN;
    for (var i = 0;i < this._shader.length;i++) {
      this._shader[i] = null;
    }
  }});
  Object.defineProperty(MeshInstance.prototype, "screenSpace", {get:function() {
    return this._screenSpace;
  }, set:function(val) {
    this._screenSpace = val;
    this._shaderDefs = val ? this._shaderDefs | pc.SHADERDEF_SCREENSPACE : this._shaderDefs & ~pc.SHADERDEF_SCREENSPACE;
    this._shader[pc.SHADER_FORWARD] = null;
  }});
  Object.defineProperty(MeshInstance.prototype, "key", {get:function() {
    return this._key[pc.SORTKEY_FORWARD];
  }, set:function(val) {
    this._key[pc.SORTKEY_FORWARD] = val;
  }});
  Object.defineProperty(MeshInstance.prototype, "mask", {get:function() {
    return this._shaderDefs >> 16;
  }, set:function(val) {
    var toggles = this._shaderDefs & 65535;
    this._shaderDefs = toggles | val << 16;
    this._shader[pc.SHADER_FORWARD] = null;
  }});
  pc.extend(MeshInstance.prototype, {syncAabb:function() {
  }, updateKey:function() {
    var material = this.material;
    this._key[pc.SORTKEY_FORWARD] = getKey(this.layer, material.blendType, false, material.id);
  }, setParameter:pc.Material.prototype.setParameter, setParameters:pc.Material.prototype.setParameters, deleteParameter:pc.Material.prototype.deleteParameter, getParameter:pc.Material.prototype.getParameter, getParameters:pc.Material.prototype.getParameters, clearParameters:pc.Material.prototype.clearParameters});
  var Command = function(layer, blendType, command) {
    this._key = [];
    this._key[pc.SORTKEY_FORWARD] = getKey(layer, blendType, true, 0);
    this.command = command;
  };
  Object.defineProperty(Command.prototype, "key", {get:function() {
    return this._key[pc.SORTKEY_FORWARD];
  }, set:function(val) {
    this._key[pc.SORTKEY_FORWARD] = val;
  }});
  var InstancingData = function(numObjects, dynamic, instanceSize) {
    instanceSize = instanceSize || 16;
    this.buffer = new Float32Array(numObjects * instanceSize);
    this.count = numObjects;
    this.offset = 0;
    this.usage = dynamic ? pc.BUFFER_DYNAMIC : pc.BUFFER_STATIC;
    this._buffer = null;
  };
  InstancingData.prototype = {update:function() {
    if (this._buffer) {
      this._buffer.setData(this.buffer);
    }
  }};
  function getKey(layer, blendType, isCommand, materialId) {
    return (layer & 15) << 27 | (blendType === pc.BLEND_NONE ? 1 : 0) << 26 | (isCommand ? 1 : 0) << 25 | (materialId & 33554431) << 0;
  }
  return {Command:Command, Mesh:Mesh, MeshInstance:MeshInstance, InstancingData:InstancingData, _getDrawcallSortKey:getKey};
}());
pc.extend(pc, function() {
  var Skin = function(graphicsDevice, ibp, boneNames) {
    this.device = graphicsDevice;
    this.inverseBindPose = ibp;
    this.boneNames = boneNames;
  };
  var SkinInstance = function(skin, node) {
    this.skin = skin;
    this.rootNode = node;
    this._dirty = true;
    this.bones = [];
    var numBones = skin.inverseBindPose.length;
    var device = skin.device;
    if (device.supportsBoneTextures) {
      var size;
      if (numBones > 256) {
        size = 64;
      } else {
        if (numBones > 64) {
          size = 32;
        } else {
          if (numBones > 16) {
            size = 16;
          } else {
            size = 8;
          }
        }
      }
      this.matrixPalette = new Float32Array(size * size * 4);
      this.boneTexture = new pc.Texture(device, {width:size, height:size, format:pc.PIXELFORMAT_RGBA32F, mipmaps:false, minFilter:pc.FILTER_NEAREST, magFilter:pc.FILTER_NEAREST});
      this.matrixPalette = this.boneTexture.lock();
    } else {
      this.matrixPalette = new Float32Array(numBones * 16);
    }
    this.matrices = [];
    for (var i = 0;i < numBones;i++) {
      this.matrices[i] = new pc.Mat4;
    }
  };
  SkinInstance.prototype = {updateMatrices:function() {
    var pos = this.rootNode.getPosition();
    for (var i = this.bones.length - 1;i >= 0;i--) {
      this.matrices[i].mul2(this.bones[i].getWorldTransform(), this.skin.inverseBindPose[i]);
      this.matrices[i].data[12] -= pos.x;
      this.matrices[i].data[13] -= pos.y;
      this.matrices[i].data[14] -= pos.z;
    }
  }, updateMatrixPalette:function() {
    var pe;
    var mp = this.matrixPalette;
    var base;
    for (var i = this.bones.length - 1;i >= 0;i--) {
      pe = this.matrices[i].data;
      base = i * 16;
      mp[base] = pe[0];
      mp[base + 1] = pe[1];
      mp[base + 2] = pe[2];
      mp[base + 3] = pe[3];
      mp[base + 4] = pe[4];
      mp[base + 5] = pe[5];
      mp[base + 6] = pe[6];
      mp[base + 7] = pe[7];
      mp[base + 8] = pe[8];
      mp[base + 9] = pe[9];
      mp[base + 10] = pe[10];
      mp[base + 11] = pe[11];
      mp[base + 12] = pe[12];
      mp[base + 13] = pe[13];
      mp[base + 14] = pe[14];
      mp[base + 15] = pe[15];
    }
    if (this.skin.device.supportsBoneTextures) {
      this.boneTexture.lock();
      this.boneTexture.unlock();
    }
  }};
  return {Skin:Skin, SkinInstance:SkinInstance};
}());
pc.extend(pc, function() {
  function PartitionedVertex() {
    this.index = 0;
    this.boneIndices = [0, 0, 0, 0];
  }
  function SkinPartition() {
    this.partition = 0;
    this.vertexStart = 0;
    this.vertexCount = 0;
    this.indexStart = 0;
    this.indexCount = 0;
    this.boneIndices = [];
    this.vertices = [];
    this.indices = [];
    this.indexMap = {};
  }
  SkinPartition.prototype = {addVertex:function(vertex, idx, vertexArray) {
    var remappedIndex = -1;
    if (this.indexMap[idx] !== undefined) {
      remappedIndex = this.indexMap[idx];
      this.indices.push(remappedIndex);
    } else {
      for (var influence = 0;influence < 4;influence++) {
        if (vertexArray.blendWeight.data[idx * 4 + influence] === 0) {
          continue;
        }
        var originalBoneIndex = vertexArray.blendIndices.data[vertex.index * 4 + influence];
        vertex.boneIndices[influence] = this.getBoneRemap(originalBoneIndex);
      }
      remappedIndex = this.vertices.length;
      this.indices.push(remappedIndex);
      this.vertices.push(vertex);
      this.indexMap[idx] = remappedIndex;
    }
  }, addPrimitive:function(vertices, vertexIndices, vertexArray, boneLimit) {
    var i, j;
    var bonesToAdd = [];
    var bonesToAddCount = 0;
    var vertexCount = vertices.length;
    for (i = 0;i < vertexCount;i++) {
      var vertex = vertices[i];
      var idx = vertex.index;
      for (var influence = 0;influence < 4;influence++) {
        if (vertexArray.blendWeight.data[idx * 4 + influence] > 0) {
          var boneIndex = vertexArray.blendIndices.data[idx * 4 + influence];
          var needToAdd = true;
          for (j = 0;j < bonesToAddCount;j++) {
            if (bonesToAdd[j] == boneIndex) {
              needToAdd = false;
              break;
            }
          }
          if (needToAdd) {
            bonesToAdd[bonesToAddCount] = boneIndex;
            var boneRemap = this.getBoneRemap(boneIndex);
            bonesToAddCount += boneRemap === -1 ? 1 : 0;
          }
        }
      }
    }
    if (this.boneIndices.length + bonesToAddCount > boneLimit) {
      return false;
    }
    for (i = 0;i < bonesToAddCount;i++) {
      this.boneIndices.push(bonesToAdd[i]);
    }
    for (i = 0;i < vertexCount;i++) {
      this.addVertex(vertices[i], vertexIndices[i], vertexArray);
    }
    return true;
  }, getBoneRemap:function(boneIndex) {
    for (var i = 0;i < this.boneIndices.length;i++) {
      if (this.boneIndices[i] === boneIndex) {
        return i;
      }
    }
    return -1;
  }};
  function indicesToReferences(model) {
    var i;
    var vertices = model.vertices;
    var skins = model.skins;
    var meshes = model.meshes;
    var meshInstances = model.meshInstances;
    for (i = 0;i < meshes.length;i++) {
      meshes[i].vertices = vertices[meshes[i].vertices];
      if (meshes[i].skin !== undefined) {
        meshes[i].skin = skins[meshes[i].skin];
      }
    }
    for (i = 0;i < meshInstances.length;i++) {
      meshInstances[i].mesh = meshes[meshInstances[i].mesh];
    }
  }
  function referencesToIndices(model) {
    var i;
    var vertices = model.vertices;
    var skins = model.skins;
    var meshes = model.meshes;
    var meshInstances = model.meshInstances;
    for (i = 0;i < meshes.length;i++) {
      meshes[i].vertices = vertices.indexOf(meshes[i].vertices);
      if (meshes[i].skin !== undefined) {
        meshes[i].skin = skins.indexOf(meshes[i].skin);
      }
    }
    for (i = 0;i < meshInstances.length;i++) {
      meshInstances[i].mesh = meshes.indexOf(meshInstances[i].mesh);
    }
  }
  function partitionSkin(model, materialMappings, boneLimit) {
    var i, j, k, index;
    indicesToReferences(model);
    var vertexArrays = model.vertices;
    var skins = model.skins;
    var mesh;
    var meshes = model.meshes;
    var meshInstances = model.meshInstances;
    var getVertex = function(idx) {
      var vert = new PartitionedVertex;
      vert.index = idx;
      return vert;
    };
    for (i = skins.length - 1;i >= 0;i--) {
      if (skins[i].boneNames.length > boneLimit) {
        var skin = skins.splice(i, 1)[0];
        var meshesToSplit = [];
        for (j = 0;j < meshes.length;j++) {
          if (meshes[j].skin === skin) {
            meshesToSplit.push(meshes[j]);
          }
        }
        for (j = 0;j < meshesToSplit.length;j++) {
          index = meshes.indexOf(meshesToSplit[j]);
          if (index !== -1) {
            meshes.splice(index, 1);
          }
        }
        if (meshesToSplit.length === 0) {
          throw new Error("partitionSkin: There should be at least one mesh that references a skin");
        }
        var vertexArray = meshesToSplit[0].vertices;
        for (j = 1;j < meshesToSplit.length;j++) {
          if (meshesToSplit[j].vertices !== vertexArray) {
            throw new Error("partitionSkin: All meshes that share a skin should also share the same vertex buffer");
          }
        }
        var partition;
        var partitions = [];
        var primitiveVertices = [];
        var primitiveIndices = [];
        var basePartition = 0;
        for (j = 0;j < meshesToSplit.length;j++) {
          mesh = meshesToSplit[j];
          var indices = mesh.indices;
          for (var iIndex = mesh.base;iIndex < mesh.base + mesh.count;) {
            index = indices[iIndex++];
            primitiveVertices[0] = getVertex(index);
            primitiveIndices[0] = index;
            index = indices[iIndex++];
            primitiveVertices[1] = getVertex(index);
            primitiveIndices[1] = index;
            index = indices[iIndex++];
            primitiveVertices[2] = getVertex(index);
            primitiveIndices[2] = index;
            var added = false;
            for (var iBonePartition = basePartition;iBonePartition < partitions.length;iBonePartition++) {
              partition = partitions[iBonePartition];
              if (partition.addPrimitive(primitiveVertices, primitiveIndices, vertexArray, boneLimit)) {
                added = true;
                break;
              }
            }
            if (!added) {
              partition = new SkinPartition;
              partition.originalMesh = mesh;
              partition.addPrimitive(primitiveVertices, primitiveIndices, vertexArray, boneLimit);
              partitions.push(partition);
            }
          }
          basePartition = partitions.length;
        }
        var partitionedVertices = [];
        var partitionedIndices = [];
        for (j = 0;j < partitions.length;j++) {
          partition = partitions[j];
          if (partition.vertices.length && partition.indices.length) {
            var vertexStart = partitionedVertices.length;
            var vertexCount = partition.vertices.length;
            var indexStart = partitionedIndices.length;
            var indexCount = partition.indices.length;
            partition.partition = j;
            partition.vertexStart = vertexStart;
            partition.vertexCount = vertexCount;
            partition.indexStart = indexStart;
            partition.indexCount = indexCount;
            var iSour;
            var iDest;
            iSour = 0;
            iDest = vertexStart;
            while (iSour < vertexCount) {
              partitionedVertices[iDest++] = partition.vertices[iSour++];
            }
            iSour = 0;
            iDest = indexStart;
            while (iSour < indexCount) {
              partitionedIndices[iDest++] = partition.indices[iSour++] + vertexStart;
            }
          }
        }
        var splitSkins = [];
        for (j = 0;j < partitions.length;j++) {
          partition = partitions[j];
          var ibp = [];
          var boneNames = [];
          for (k = 0;k < partition.boneIndices.length;k++) {
            ibp.push(skin.inverseBindMatrices[partition.boneIndices[k]]);
            boneNames.push(skin.boneNames[partition.boneIndices[k]]);
          }
          var splitSkin = {inverseBindMatrices:ibp, boneNames:boneNames};
          splitSkins.push(splitSkin);
          skins.push(splitSkin);
        }
        var attrib, attribName, data, components;
        var splitVertexArray = {};
        for (attribName in vertexArray) {
          splitVertexArray[attribName] = {components:vertexArray[attribName].components, data:[], type:vertexArray[attribName].type};
        }
        for (attribName in vertexArray) {
          if (attribName === "blendIndices") {
            var dstBoneIndices = splitVertexArray[attribName].data;
            for (j = 0;j < partitionedVertices.length;j++) {
              var srcBoneIndices = partitionedVertices[j].boneIndices;
              dstBoneIndices.push(srcBoneIndices[0], srcBoneIndices[1], srcBoneIndices[2], srcBoneIndices[3]);
            }
          } else {
            attrib = vertexArray[attribName];
            data = attrib.data;
            components = attrib.components;
            for (j = 0;j < partitionedVertices.length;j++) {
              index = partitionedVertices[j].index;
              for (k = 0;k < components;k++) {
                splitVertexArray[attribName].data.push(data[index * components + k]);
              }
            }
          }
        }
        vertexArrays[vertexArrays.indexOf(vertexArray)] = splitVertexArray;
        var base = 0;
        for (j = 0;j < partitions.length;j++) {
          partition = partitions[j];
          mesh = {aabb:{min:[0, 0, 0], max:[0, 0, 0]}, vertices:splitVertexArray, skin:splitSkins[j], indices:partitionedIndices.splice(0, partition.indexCount), type:"triangles", base:0, count:partition.indexCount};
          meshes.push(mesh);
          for (k = meshInstances.length - 1;k >= 0;k--) {
            if (meshInstances[k].mesh === partition.originalMesh) {
              meshInstances.push({mesh:mesh, node:meshInstances[k].node});
              if (materialMappings) {
                materialMappings.push({material:materialMappings[k].material, path:materialMappings[k].path});
              }
            }
          }
          base += partition.indexCount;
        }
        for (j = 0;j < partitions.length;j++) {
          partition = partitions[j];
          for (k = meshInstances.length - 1;k >= 0;k--) {
            if (meshInstances[k].mesh === partition.originalMesh) {
              meshInstances.splice(k, 1);
              if (materialMappings) {
                materialMappings.splice(k, 1);
              }
            }
          }
        }
      }
    }
    referencesToIndices(model);
  }
  return {partitionSkin:partitionSkin};
}());
pc.extend(pc, function() {
  var Model = function Model() {
    this.graph = null;
    this.meshInstances = [];
    this.skinInstances = [];
    this.cameras = [];
    this.lights = [];
    this._shadersVersion = 0;
  };
  Model.prototype = {getGraph:function() {
    return this.graph;
  }, setGraph:function(graph) {
    this.graph = graph;
  }, getCameras:function() {
    return this.cameras;
  }, setCameras:function(cameras) {
    this.cameras = cameras;
  }, getLights:function() {
    return this.lights;
  }, setLights:function(lights) {
    this.lights = lights;
  }, getMaterials:function() {
    var i;
    var materials = [];
    for (i = 0;i < this.meshInstances.length;i++) {
      var meshInstance = this.meshInstances[i];
      if (materials.indexOf(meshInstance.material) === -1) {
        materials.push(meshInstance.material);
      }
    }
    return materials;
  }, clone:function() {
    var i, j;
    var srcNodes = [];
    var cloneNodes = [];
    var _duplicate = function(node) {
      var newNode = node.clone();
      srcNodes.push(node);
      cloneNodes.push(newNode);
      for (var i = 0;i < node._children.length;i++) {
        newNode.addChild(_duplicate(node._children[i]));
      }
      return newNode;
    };
    var cloneGraph = _duplicate(this.graph);
    var cloneMeshInstances = [];
    var cloneSkinInstances = [];
    for (i = 0;i < this.skinInstances.length;i++) {
      var skin = this.skinInstances[i].skin;
      var cloneSkinInstance = new pc.SkinInstance(skin, cloneGraph);
      var bones = [];
      for (j = 0;j < skin.boneNames.length;j++) {
        var boneName = skin.boneNames[j];
        var bone = cloneGraph.findByName(boneName);
        bones.push(bone);
      }
      cloneSkinInstance.bones = bones;
      cloneSkinInstances.push(cloneSkinInstance);
    }
    for (i = 0;i < this.meshInstances.length;i++) {
      var meshInstance = this.meshInstances[i];
      var nodeIndex = srcNodes.indexOf(meshInstance.node);
      var cloneMeshInstance = new pc.MeshInstance(cloneNodes[nodeIndex], meshInstance.mesh, meshInstance.material);
      if (meshInstance.skinInstance) {
        var skinInstanceIndex = this.skinInstances.indexOf(meshInstance.skinInstance);
        cloneMeshInstance.skinInstance = cloneSkinInstances[skinInstanceIndex];
      }
      cloneMeshInstances.push(cloneMeshInstance);
    }
    var clone = new pc.Model;
    clone.graph = cloneGraph;
    clone.meshInstances = cloneMeshInstances;
    clone.skinInstances = cloneSkinInstances;
    clone.getGraph().syncHierarchy();
    return clone;
  }, destroy:function() {
    var meshes = this.meshInstances;
    var meshInstance, mesh, skin, ib, boneTex, j;
    var device;
    for (var i = 0;i < meshes.length;i++) {
      meshInstance = meshes[i];
      mesh = meshInstance.mesh;
      if (mesh) {
        mesh._refCount--;
        if (mesh._refCount < 1) {
          if (mesh.vertexBuffer) {
            device = device || mesh.vertexBuffer.device;
            mesh.vertexBuffer.destroy();
            mesh.vertexBuffer = null;
          }
          for (j = 0;j < mesh.indexBuffer.length;j++) {
            device = device || mesh.indexBuffer.device;
            ib = mesh.indexBuffer[j];
            if (!ib) {
              continue;
            }
            ib.destroy();
          }
          mesh.indexBuffer.length = 0;
        }
      }
      skin = meshInstance.skinInstance;
      if (skin) {
        boneTex = skin.boneTexture;
        if (boneTex) {
          boneTex.destroy();
        }
      }
      meshInstance.skinInstance = null;
    }
    if (device) {
      device.onVertexBufferDeleted();
    }
  }, generateWireframe:function() {
    var i, j, k;
    var i1, i2;
    var mesh, base, count, indexBuffer, wireBuffer;
    var srcIndices, dstIndices;
    var meshes = [];
    for (i = 0;i < this.meshInstances.length;i++) {
      mesh = this.meshInstances[i].mesh;
      if (meshes.indexOf(mesh) === -1) {
        meshes.push(mesh);
      }
    }
    var offsets = [[0, 1], [1, 2], [2, 0]];
    for (i = 0;i < meshes.length;i++) {
      mesh = meshes[i];
      base = mesh.primitive[pc.RENDERSTYLE_SOLID].base;
      count = mesh.primitive[pc.RENDERSTYLE_SOLID].count;
      indexBuffer = mesh.indexBuffer[pc.RENDERSTYLE_SOLID];
      srcIndices = new Uint16Array(indexBuffer.lock());
      var uniqueLineIndices = {};
      var lines = [];
      for (j = base;j < base + count;j += 3) {
        for (k = 0;k < 3;k++) {
          i1 = srcIndices[j + offsets[k][0]];
          i2 = srcIndices[j + offsets[k][1]];
          var line = i1 > i2 ? i2 << 16 | i1 : i1 << 16 | i2;
          if (uniqueLineIndices[line] === undefined) {
            uniqueLineIndices[line] = 0;
            lines.push(i1, i2);
          }
        }
      }
      indexBuffer.unlock();
      wireBuffer = new pc.IndexBuffer(indexBuffer.device, pc.INDEXFORMAT_UINT16, lines.length);
      dstIndices = new Uint16Array(wireBuffer.lock());
      dstIndices.set(lines);
      wireBuffer.unlock();
      mesh.primitive[pc.RENDERSTYLE_WIREFRAME] = {type:pc.PRIMITIVE_LINES, base:0, count:lines.length, indexed:true};
      mesh.indexBuffer[pc.RENDERSTYLE_WIREFRAME] = wireBuffer;
    }
  }};
  return {Model:Model};
}());
pc.extend(pc, function() {
  var particleVerts = [[-1, -1], [1, -1], [1, 1], [-1, 1]];
  var _createTexture = function(device, width, height, pixelData, format, mult8Bit, filter) {
    if (!format) {
      format = pc.PIXELFORMAT_RGBA32F;
    }
    var mipFilter = pc.FILTER_NEAREST;
    if (filter && format === pc.PIXELFORMAT_R8_G8_B8_A8) {
      mipFilter = pc.FILTER_LINEAR;
    }
    var texture = new pc.Texture(device, {width:width, height:height, format:format, cubemap:false, mipmaps:false, minFilter:mipFilter, magFilter:mipFilter});
    var pixels = texture.lock();
    if (format === pc.PIXELFORMAT_R8_G8_B8_A8) {
      var temp = new Uint8Array(pixelData.length);
      for (var i = 0;i < pixelData.length;i++) {
        temp[i] = pixelData[i] * mult8Bit * 255;
      }
      pixelData = temp;
    }
    pixels.set(pixelData);
    texture.unlock();
    return texture;
  };
  function saturate(x) {
    return Math.max(Math.min(x, 1), 0);
  }
  function glMod(x, y) {
    return x - y * Math.floor(x / y);
  }
  var default0Curve = new pc.Curve([0, 0, 1, 0]);
  var default1Curve = new pc.Curve([0, 1, 1, 1]);
  var default0Curve3 = new pc.CurveSet([0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]);
  var default1Curve3 = new pc.CurveSet([0, 1, 1, 1], [0, 1, 1, 1], [0, 1, 1, 1]);
  var particleTexHeight = 2;
  var particleTexChannels = 4;
  var velocityVec = new pc.Vec3;
  var localVelocityVec = new pc.Vec3;
  var velocityVec2 = new pc.Vec3;
  var localVelocityVec2 = new pc.Vec3;
  var rndFactor3Vec = new pc.Vec3;
  var particlePosPrev = new pc.Vec3;
  var particlePos = new pc.Vec3;
  var particleFinalPos = new pc.Vec3;
  var moveDirVec = new pc.Vec3;
  var rotMat = new pc.Mat4;
  var spawnMatrix3 = new pc.Mat3;
  var emitterMatrix3 = new pc.Mat3;
  var uniformScale = 1;
  var nonUniformScale;
  var spawnMatrix = new pc.Mat4;
  var randomPos = new pc.Vec3;
  var randomPosTformed = new pc.Vec3;
  var tmpVec3 = new pc.Vec3;
  var velocityV = new pc.Vec3;
  var bMin = new pc.Vec3;
  var bMax = new pc.Vec3;
  var setPropertyTarget;
  var setPropertyOptions;
  function setProperty(pName, defaultVal) {
    if (setPropertyOptions[pName] !== undefined && setPropertyOptions[pName] !== null) {
      setPropertyTarget[pName] = setPropertyOptions[pName];
    } else {
      setPropertyTarget[pName] = defaultVal;
    }
  }
  function pack3NFloats(a, b, c) {
    var packed = a * 255 << 16 | b * 255 << 8 | c * 255;
    return packed / (1 << 24);
  }
  function packTextureXYZ_NXYZ(qXYZ, qXYZ2) {
    var num = qXYZ.length / 3;
    var colors = new Array(num * 4);
    for (var i = 0;i < num;i++) {
      colors[i * 4] = qXYZ[i * 3];
      colors[i * 4 + 1] = qXYZ[i * 3 + 1];
      colors[i * 4 + 2] = qXYZ[i * 3 + 2];
      colors[i * 4 + 3] = pack3NFloats(qXYZ2[i * 3], qXYZ2[i * 3 + 1], qXYZ2[i * 3 + 2]);
    }
    return colors;
  }
  function packTextureRGBA(qRGB, qA) {
    var colors = new Array(qA.length * 4);
    for (var i = 0;i < qA.length;i++) {
      colors[i * 4] = qRGB[i * 3];
      colors[i * 4 + 1] = qRGB[i * 3 + 1];
      colors[i * 4 + 2] = qRGB[i * 3 + 2];
      colors[i * 4 + 3] = qA[i];
    }
    return colors;
  }
  function packTexture5Floats(qA, qB, qC, qD, qE) {
    var colors = new Array(qA.length * 4);
    for (var i = 0;i < qA.length;i++) {
      colors[i * 4] = qA[i];
      colors[i * 4 + 1] = qB[i];
      colors[i * 4 + 2] = 0;
      colors[i * 4 + 3] = pack3NFloats(qC[i], qD[i], qE[i]);
    }
    return colors;
  }
  function syncToCpu(device, targ) {
    var tex = targ._colorBuffer;
    var pixels = new Uint8Array(tex.width * tex.height * 4);
    var gl = device.gl;
    gl.bindFramebuffer(gl.FRAMEBUFFER, targ._glFrameBuffer);
    gl.readPixels(0, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
    if (!tex._levels) {
      tex._levels = [];
    }
    tex._levels[0] = pixels;
  }
  var ParticleEmitter = function(graphicsDevice, options) {
    this.graphicsDevice = graphicsDevice;
    var gd = graphicsDevice;
    var precision = 32;
    this.precision = precision;
    this._addTimeTime = 0;
    if (!ParticleEmitter.DEFAULT_PARAM_TEXTURE) {
      var resolution = 16;
      var centerPoint = resolution * .5 + .5;
      var dtex = new Float32Array(resolution * resolution * 4);
      var x, y, xgrad, ygrad, p, c;
      for (y = 0;y < resolution;y++) {
        for (x = 0;x < resolution;x++) {
          xgrad = x + 1 - centerPoint;
          ygrad = y + 1 - centerPoint;
          c = saturate(1 - saturate(Math.sqrt(xgrad * xgrad + ygrad * ygrad) / resolution) - .5);
          p = y * resolution + x;
          dtex[p * 4] = 1;
          dtex[p * 4 + 1] = 1;
          dtex[p * 4 + 2] = 1;
          dtex[p * 4 + 3] = c;
        }
      }
      ParticleEmitter.DEFAULT_PARAM_TEXTURE = _createTexture(gd, resolution, resolution, dtex, pc.PIXELFORMAT_R8_G8_B8_A8, 1, true);
      ParticleEmitter.DEFAULT_PARAM_TEXTURE.minFilter = pc.FILTER_LINEAR;
      ParticleEmitter.DEFAULT_PARAM_TEXTURE.magFilter = pc.FILTER_LINEAR;
    }
    setPropertyTarget = this;
    setPropertyOptions = options;
    setProperty("numParticles", 1);
    if (this.numParticles > graphicsDevice.maxTextureSize) {
      console.warn("WARNING: can't create more than " + graphicsDevice.maxTextureSize + " particles on this device.");
      this.numParticles = graphicsDevice.maxTextureSize;
    }
    setProperty("rate", 1);
    setProperty("rate2", this.rate);
    setProperty("lifetime", 50);
    setProperty("emitterExtents", new pc.Vec3(0, 0, 0));
    setProperty("emitterRadius", 0);
    setProperty("emitterShape", pc.EMITTERSHAPE_BOX);
    setProperty("initialVelocity", 1);
    setProperty("wrap", false);
    setProperty("localSpace", false);
    setProperty("wrapBounds", null);
    setProperty("colorMap", ParticleEmitter.DEFAULT_PARAM_TEXTURE);
    setProperty("normalMap", null);
    setProperty("loop", true);
    setProperty("preWarm", false);
    setProperty("sort", pc.PARTICLESORT_NONE);
    setProperty("mode", pc.PARTICLEMODE_GPU);
    setProperty("scene", null);
    setProperty("lighting", false);
    setProperty("halfLambert", false);
    setProperty("intensity", 1);
    setProperty("stretch", 0);
    setProperty("alignToMotion", false);
    setProperty("depthSoftening", 0);
    setProperty("mesh", null);
    setProperty("depthWrite", false);
    setProperty("noFog", false);
    setProperty("blendType", pc.BLEND_NORMAL);
    setProperty("node", null);
    setProperty("startAngle", 0);
    setProperty("startAngle2", this.startAngle);
    setProperty("animTilesX", 1);
    setProperty("animTilesY", 1);
    setProperty("animNumFrames", 1);
    setProperty("animSpeed", 1);
    setProperty("animLoop", true);
    this.frameRandom = new pc.Vec3(0, 0, 0);
    setProperty("colorGraph", default1Curve3);
    setProperty("colorGraph2", this.colorGraph);
    setProperty("scaleGraph", default1Curve);
    setProperty("scaleGraph2", this.scaleGraph);
    setProperty("alphaGraph", default1Curve);
    setProperty("alphaGraph2", this.alphaGraph);
    setProperty("localVelocityGraph", default0Curve3);
    setProperty("localVelocityGraph2", this.localVelocityGraph);
    setProperty("velocityGraph", default0Curve3);
    setProperty("velocityGraph2", this.velocityGraph);
    setProperty("rotationSpeedGraph", default0Curve);
    setProperty("rotationSpeedGraph2", this.rotationSpeedGraph);
    this.constantParticleTexIN = gd.scope.resolve("particleTexIN");
    this.constantParticleTexOUT = gd.scope.resolve("particleTexOUT");
    this.constantEmitterPos = gd.scope.resolve("emitterPos");
    this.constantEmitterScale = gd.scope.resolve("emitterScale");
    this.constantSpawnBounds = gd.scope.resolve("spawnBounds");
    this.constantSpawnBoundsSphere = gd.scope.resolve("spawnBoundsSphere");
    this.constantInitialVelocity = gd.scope.resolve("initialVelocity");
    this.constantFrameRandom = gd.scope.resolve("frameRandom");
    this.constantDelta = gd.scope.resolve("delta");
    this.constantRate = gd.scope.resolve("rate");
    this.constantRateDiv = gd.scope.resolve("rateDiv");
    this.constantLifetime = gd.scope.resolve("lifetime");
    this.constantLightCube = gd.scope.resolve("lightCube[0]");
    this.constantGraphSampleSize = gd.scope.resolve("graphSampleSize");
    this.constantGraphNumSamples = gd.scope.resolve("graphNumSamples");
    this.constantInternalTex0 = gd.scope.resolve("internalTex0");
    this.constantInternalTex1 = gd.scope.resolve("internalTex1");
    this.constantInternalTex2 = gd.scope.resolve("internalTex2");
    this.constantEmitterMatrix = gd.scope.resolve("emitterMatrix");
    this.constantNumParticles = gd.scope.resolve("numParticles");
    this.constantNumParticlesPot = gd.scope.resolve("numParticlesPot");
    this.constantLocalVelocityDivMult = gd.scope.resolve("localVelocityDivMult");
    this.constantVelocityDivMult = gd.scope.resolve("velocityDivMult");
    this.constantRotSpeedDivMult = gd.scope.resolve("rotSpeedDivMult");
    this.constantSeed = gd.scope.resolve("seed");
    this.constantStartAngle = gd.scope.resolve("startAngle");
    this.constantStartAngle2 = gd.scope.resolve("startAngle2");
    this.constantOutBoundsMul = gd.scope.resolve("outBoundsMul");
    this.constantOutBoundsAdd = gd.scope.resolve("outBoundsAdd");
    this.constantInBoundsSize = gd.scope.resolve("inBoundsSize");
    this.constantInBoundsCenter = gd.scope.resolve("inBoundsCenter");
    this.constantMaxVel = gd.scope.resolve("maxVel");
    this.lightCube = new Float32Array(6 * 3);
    this.lightCubeDir = new Array(6);
    this.lightCubeDir[0] = new pc.Vec3(-1, 0, 0);
    this.lightCubeDir[1] = new pc.Vec3(1, 0, 0);
    this.lightCubeDir[2] = new pc.Vec3(0, -1, 0);
    this.lightCubeDir[3] = new pc.Vec3(0, 1, 0);
    this.lightCubeDir[4] = new pc.Vec3(0, 0, -1);
    this.lightCubeDir[5] = new pc.Vec3(0, 0, 1);
    this.animParams = new pc.Vec4;
    this.internalTex0 = null;
    this.internalTex1 = null;
    this.internalTex2 = null;
    this.internalTex3 = null;
    this.vbToSort = null;
    this.vbOld = null;
    this.particleDistance = null;
    this.camera = null;
    this.swapTex = false;
    this.useMesh = true;
    this.useCpu = false;
    this.pack8 = true;
    this.localBounds = new pc.BoundingBox;
    this.worldBoundsNoTrail = new pc.BoundingBox;
    this.worldBoundsTrail = [new pc.BoundingBox, new pc.BoundingBox];
    this.worldBounds = new pc.BoundingBox;
    this.worldBoundsSize = new pc.Vec3;
    this.prevWorldBoundsSize = new pc.Vec3;
    this.prevWorldBoundsCenter = new pc.Vec3;
    this.worldBoundsMul = new pc.Vec3;
    this.worldBoundsAdd = new pc.Vec3;
    this.timeToSwitchBounds = 0;
    this.shaderParticleUpdateRespawn = null;
    this.shaderParticleUpdateNoRespawn = null;
    this.shaderParticleUpdateOnStop = null;
    this.numParticleVerts = 0;
    this.numParticleIndices = 0;
    this.material = null;
    this.meshInstance = null;
    this.seed = 0;
    this.fixedTimeStep = 1 / 60;
    this.maxSubSteps = 10;
    this.simTime = 0;
    this.simTimeTotal = 0;
    this.beenReset = false;
    this.rebuild();
  };
  function calcEndTime(emitter) {
    var interval = Math.max(emitter.rate, emitter.rate2) * emitter.numParticles + emitter.lifetime;
    return Date.now() + interval * 1E3;
  }
  function subGraph(A, B) {
    var r = new Float32Array(A.length);
    for (var i = 0;i < A.length;i++) {
      r[i] = A[i] - B[i];
    }
    return r;
  }
  function maxUnsignedGraphValue(A, outUMax) {
    var i, j;
    var chans = outUMax.length;
    var values = A.length / chans;
    for (i = 0;i < values;i++) {
      for (j = 0;j < chans;j++) {
        var a = Math.abs(A[i * chans + j]);
        outUMax[j] = Math.max(outUMax[j], a);
      }
    }
  }
  function normalizeGraph(A, uMax) {
    var chans = uMax.length;
    var i, j;
    var values = A.length / chans;
    for (i = 0;i < values;i++) {
      for (j = 0;j < chans;j++) {
        A[i * chans + j] /= uMax[j];
        A[i * chans + j] *= .5;
        A[i * chans + j] += .5;
      }
    }
  }
  function divGraphFrom2Curves(curve1, curve2, outUMax) {
    var sub = subGraph(curve2, curve1);
    maxUnsignedGraphValue(sub, outUMax);
    normalizeGraph(sub, outUMax);
    return sub;
  }
  function mat4ToMat3(mat4, mat3) {
    mat3.data[0] = mat4.data[0];
    mat3.data[1] = mat4.data[1];
    mat3.data[2] = mat4.data[2];
    mat3.data[3] = mat4.data[4];
    mat3.data[4] = mat4.data[5];
    mat3.data[5] = mat4.data[6];
    mat3.data[6] = mat4.data[8];
    mat3.data[7] = mat4.data[9];
    mat3.data[8] = mat4.data[10];
  }
  ParticleEmitter.prototype = {onChangeCamera:function() {
    if (this.depthSoftening > 0) {
      if (this.camera) {
        this.camera.requestDepthMap();
      }
    }
    this.regenShader();
    this.resetMaterial();
  }, calculateBoundsMad:function() {
    this.worldBoundsMul.x = 1 / this.worldBoundsSize.x;
    this.worldBoundsMul.y = 1 / this.worldBoundsSize.y;
    this.worldBoundsMul.z = 1 / this.worldBoundsSize.z;
    this.worldBoundsAdd.copy(this.worldBounds.center).mul(this.worldBoundsMul).scale(-1);
    this.worldBoundsAdd.x += .5;
    this.worldBoundsAdd.y += .5;
    this.worldBoundsAdd.z += .5;
  }, calculateWorldBounds:function() {
    if (!this.node) {
      return;
    }
    var pos = this.node.getPosition();
    this.prevWorldBoundsSize.copy(this.worldBoundsSize);
    this.prevWorldBoundsCenter.copy(this.worldBounds.center);
    this.worldBoundsNoTrail.setFromTransformedAabb(this.localBounds, this.node.getWorldTransform());
    this.worldBoundsTrail[0].add(this.worldBoundsNoTrail);
    var now = this.simTimeTotal;
    if (now > this.timeToSwitchBounds) {
      var tmp = this.worldBoundsTrail[0];
      this.worldBoundsTrail[0] = this.worldBoundsTrail[1];
      this.worldBoundsTrail[1] = tmp;
      this.worldBoundsTrail[0].copy(this.worldBoundsNoTrail);
      this.timeToSwitchBounds = now + this.lifetime;
    }
    this.worldBounds.copy(this.worldBoundsTrail[0]);
    this.worldBounds.add(this.worldBoundsTrail[1]);
    this.worldBoundsSize.copy(this.worldBounds.halfExtents).scale(2);
    this.meshInstance.mesh.aabb = this.worldBounds;
    this.meshInstance._aabbVer = 1 - this.meshInstance._aabbVer;
    if (this.pack8) {
      this.calculateBoundsMad();
    }
  }, calculateLocalBounds:function() {
    var minx = Number.MAX_VALUE;
    var miny = Number.MAX_VALUE;
    var minz = Number.MAX_VALUE;
    var maxx = -Number.MAX_VALUE;
    var maxy = -Number.MAX_VALUE;
    var maxz = -Number.MAX_VALUE;
    var maxScale = 0;
    var stepWeight = this.lifetime / this.precision;
    var vels = [this.qVelocity, this.qVelocity2, this.qLocalVelocity, this.qLocalVelocity2];
    var accumX = [0, 0, 0, 0];
    var accumY = [0, 0, 0, 0];
    var accumZ = [0, 0, 0, 0];
    var i, j;
    var index;
    var x, y, z;
    for (i = 0;i < this.precision + 1;i++) {
      index = Math.min(i, this.precision - 1);
      for (j = 0;j < 4;j++) {
        x = vels[j][index * 3] * stepWeight + accumX[j];
        y = vels[j][index * 3 + 1] * stepWeight + accumY[j];
        z = vels[j][index * 3 + 2] * stepWeight + accumZ[j];
        if (minx > x) {
          minx = x;
        }
        if (miny > y) {
          miny = y;
        }
        if (minz > z) {
          minz = z;
        }
        if (maxx < x) {
          maxx = x;
        }
        if (maxy < y) {
          maxy = y;
        }
        if (maxz < z) {
          maxz = z;
        }
        accumX[j] = x;
        accumY[j] = y;
        accumZ[j] = z;
      }
      maxScale = Math.max(maxScale, this.qScale[index]);
    }
    if (this.emitterShape === pc.EMITTERSHAPE_BOX) {
      x = this.emitterExtents.x * .5;
      y = this.emitterExtents.y * .5;
      z = this.emitterExtents.z * .5;
      if (maxx < x) {
        maxx = x;
      }
      if (maxy < y) {
        maxy = y;
      }
      if (maxz < z) {
        maxz = z;
      }
      x = -x;
      y = -y;
      z = -z;
      if (minx > x) {
        minx = x;
      }
      if (miny > y) {
        miny = y;
      }
      if (minz > z) {
        minz = z;
      }
    } else {
      x = this.emitterRadius;
      y = this.emitterRadius;
      z = this.emitterRadius;
      if (maxx < x) {
        maxx = x;
      }
      if (maxy < y) {
        maxy = y;
      }
      if (maxz < z) {
        maxz = z;
      }
      x = -x;
      y = -y;
      z = -z;
      if (minx > x) {
        minx = x;
      }
      if (miny > y) {
        miny = y;
      }
      if (minz > z) {
        minz = z;
      }
    }
    bMin.x = minx - maxScale;
    bMin.y = miny - maxScale;
    bMin.z = minz - maxScale;
    bMax.x = maxx + maxScale;
    bMax.y = maxy + maxScale;
    bMax.z = maxz + maxScale;
    this.localBounds.setMinMax(bMin, bMax);
  }, rebuild:function() {
    var i, len;
    var precision = this.precision;
    var gd = this.graphicsDevice;
    if (this.colorMap === null) {
      this.colorMap = ParticleEmitter.DEFAULT_PARAM_TEXTURE;
    }
    this.spawnBounds = this.emitterShape === pc.EMITTERSHAPE_BOX ? this.emitterExtents : this.emitterRadius;
    this.useCpu = this.useCpu || this.sort > pc.PARTICLESORT_NONE || gd.maxVertexTextures <= 1 || gd.fragmentUniformsCount < 64 || gd.forceCpuParticles || !gd.extTextureFloat;
    this.vertexBuffer = undefined;
    this.pack8 = (this.pack8 || !gd.extTextureFloatRenderable) && !this.useCpu;
    particleTexHeight = this.useCpu || this.pack8 ? 4 : 2;
    this.useMesh = false;
    if (this.mesh) {
      var totalVertCount = this.numParticles * this.mesh.vertexBuffer.numVertices;
      if (totalVertCount > 65535) {
        console.warn("WARNING: particle system can't render mesh particles because numParticles * numVertices is more than 65k. Reverting to quad particles.");
      } else {
        this.useMesh = true;
      }
    }
    this.numParticlesPot = pc.math.nextPowerOfTwo(this.numParticles);
    this.rebuildGraphs();
    this.calculateLocalBounds();
    if (this.node) {
      this.worldBounds.setFromTransformedAabb(this.localBounds, this.node.getWorldTransform());
      this.worldBoundsTrail[0].copy(this.worldBounds);
      this.worldBoundsTrail[1].copy(this.worldBounds);
      this.worldBoundsSize.copy(this.worldBounds.halfExtents).scale(2);
      this.prevWorldBoundsSize.copy(this.worldBoundsSize);
      this.prevWorldBoundsCenter.copy(this.worldBounds.center);
      if (this.pack8) {
        this.calculateBoundsMad();
      }
    }
    this.vbToSort = new Array(this.numParticles);
    this.particleDistance = new Float32Array(this.numParticles);
    this.frameRandom.x = Math.random();
    this.frameRandom.y = Math.random();
    this.frameRandom.z = Math.random();
    this.particleTex = new Float32Array(this.numParticlesPot * particleTexHeight * particleTexChannels);
    var emitterPos = this.node === null || this.localSpace ? pc.Vec3.ZERO : this.node.getPosition();
    if (this.emitterShape === pc.EMITTERSHAPE_BOX) {
      if (this.node === null) {
        spawnMatrix.setTRS(pc.Vec3.ZERO, pc.Quat.IDENTITY, this.spawnBounds);
      } else {
        spawnMatrix.setTRS(pc.Vec3.ZERO, this.node.getRotation(), tmpVec3.copy(this.spawnBounds).mul(this.node.localScale));
      }
    }
    for (i = 0;i < this.numParticles;i++) {
      this.calcSpawnPosition(emitterPos, i);
      if (this.useCpu) {
        this.particleTex[i * particleTexChannels + 3 + this.numParticlesPot * 2 * particleTexChannels] = 1;
      }
    }
    this.particleTexStart = new Float32Array(this.numParticlesPot * particleTexHeight * particleTexChannels);
    for (i = 0;i < this.particleTexStart.length;i++) {
      this.particleTexStart[i] = this.particleTex[i];
    }
    if (!this.useCpu) {
      if (this.pack8) {
        this.particleTexIN = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTex, pc.PIXELFORMAT_R8_G8_B8_A8, 1, false);
        this.particleTexOUT = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTex, pc.PIXELFORMAT_R8_G8_B8_A8, 1, false);
        this.particleTexStart = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTexStart, pc.PIXELFORMAT_R8_G8_B8_A8, 1, false);
      } else {
        this.particleTexIN = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTex);
        this.particleTexOUT = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTex);
        this.particleTexStart = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTexStart);
      }
      this.rtParticleTexIN = new pc.RenderTarget(gd, this.particleTexIN, {depth:false});
      this.rtParticleTexOUT = new pc.RenderTarget(gd, this.particleTexOUT, {depth:false});
      this.swapTex = false;
    }
    var chunks = pc.shaderChunks;
    var shaderCodeStart = chunks.particleUpdaterInitPS + (this.pack8 ? chunks.particleInputRgba8PS + chunks.particleOutputRgba8PS : chunks.particleInputFloatPS + chunks.particleOutputFloatPS) + (this.emitterShape === pc.EMITTERSHAPE_BOX ? chunks.particleUpdaterAABBPS : chunks.particleUpdaterSpherePS) + chunks.particleUpdaterStartPS;
    var shaderCodeRespawn = shaderCodeStart + chunks.particleUpdaterRespawnPS + chunks.particleUpdaterEndPS;
    var shaderCodeNoRespawn = shaderCodeStart + chunks.particleUpdaterNoRespawnPS + chunks.particleUpdaterEndPS;
    var shaderCodeOnStop = shaderCodeStart + chunks.particleUpdaterOnStopPS + chunks.particleUpdaterEndPS;
    this.shaderParticleUpdateRespawn = chunks.createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeRespawn, "fsQuad0" + this.emitterShape + "" + this.pack8);
    this.shaderParticleUpdateNoRespawn = chunks.createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeNoRespawn, "fsQuad1" + this.emitterShape + "" + this.pack8);
    this.shaderParticleUpdateOnStop = chunks.createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeOnStop, "fsQuad2" + this.emitterShape + "" + this.pack8);
    this.numParticleVerts = this.useMesh ? this.mesh.vertexBuffer.numVertices : 4;
    this.numParticleIndices = this.useMesh ? this.mesh.indexBuffer[0].numIndices : 6;
    this._allocate(this.numParticles);
    var mesh = new pc.Mesh;
    mesh.vertexBuffer = this.vertexBuffer;
    mesh.indexBuffer[0] = this.indexBuffer;
    mesh.primitive[0].type = pc.PRIMITIVE_TRIANGLES;
    mesh.primitive[0].base = 0;
    mesh.primitive[0].count = this.numParticles * this.numParticleIndices;
    mesh.primitive[0].indexed = true;
    this.material = new pc.Material;
    this.material.cullMode = pc.CULLFACE_NONE;
    this.material.alphaWrite = false;
    this.material.blend = true;
    this.material.blendType = this.blendType;
    this.material.depthWrite = this.depthWrite;
    this.material.emitter = this;
    this.regenShader();
    this.resetMaterial();
    this.meshInstance = new pc.MeshInstance(this.node, mesh, this.material);
    this.meshInstance.updateKey();
    this.meshInstance.drawToDepth = false;
    this.meshInstance.cull = true;
    this.meshInstance.aabb = this.worldBounds;
    this.meshInstance._updateAabb = false;
    this._initializeTextures();
    this.addTime(0);
    if (this.preWarm) {
      this.prewarm(this.lifetime);
    }
    this.resetTime();
  }, _isAnimated:function() {
    return this.animNumFrames >= 1 && (this.animTilesX > 1 || this.animTilesY > 1) && (this.colorMap && this.colorMap !== ParticleEmitter.DEFAULT_PARAM_TEXTURE || this.normalMap);
  }, calcSpawnPosition:function(emitterPos, i) {
    var rX = Math.random();
    var rY = Math.random();
    var rZ = Math.random();
    var rW = Math.random();
    if (this.useCpu) {
      this.particleTex[i * particleTexChannels + 0 + this.numParticlesPot * 2 * particleTexChannels] = rX;
      this.particleTex[i * particleTexChannels + 1 + this.numParticlesPot * 2 * particleTexChannels] = rY;
      this.particleTex[i * particleTexChannels + 2 + this.numParticlesPot * 2 * particleTexChannels] = rZ;
    }
    randomPos.data[0] = rX - .5;
    randomPos.data[1] = rY - .5;
    randomPos.data[2] = rZ - .5;
    if (this.emitterShape === pc.EMITTERSHAPE_BOX) {
      randomPosTformed.copy(emitterPos).add(spawnMatrix.transformPoint(randomPos));
    } else {
      randomPos.normalize();
      randomPosTformed.copy(emitterPos).add(randomPos.scale(rW * this.spawnBounds));
    }
    if (this.pack8) {
      var packX = (randomPosTformed.data[0] - this.worldBounds.center.data[0]) / this.worldBoundsSize.data[0] + .5;
      var packY = (randomPosTformed.data[1] - this.worldBounds.center.data[1]) / this.worldBoundsSize.data[1] + .5;
      var packZ = (randomPosTformed.data[2] - this.worldBounds.center.data[2]) / this.worldBoundsSize.data[2] + .5;
      var packA = pc.math.lerp(this.startAngle * pc.math.DEG_TO_RAD, this.startAngle2 * pc.math.DEG_TO_RAD, rX);
      packA = packA % (Math.PI * 2) / (Math.PI * 2);
      var rg0 = encodeFloatRG(packX);
      this.particleTex[i * particleTexChannels] = rg0[0];
      this.particleTex[i * particleTexChannels + 1] = rg0[1];
      var ba0 = encodeFloatRG(packY);
      this.particleTex[i * particleTexChannels + 2] = ba0[0];
      this.particleTex[i * particleTexChannels + 3] = ba0[1];
      var rg1 = encodeFloatRG(packZ);
      this.particleTex[i * particleTexChannels + 0 + this.numParticlesPot * particleTexChannels] = rg1[0];
      this.particleTex[i * particleTexChannels + 1 + this.numParticlesPot * particleTexChannels] = rg1[1];
      var ba1 = encodeFloatRG(packA);
      this.particleTex[i * particleTexChannels + 2 + this.numParticlesPot * particleTexChannels] = ba1[0];
      this.particleTex[i * particleTexChannels + 3 + this.numParticlesPot * particleTexChannels] = ba1[1];
      var a2 = 1;
      this.particleTex[i * particleTexChannels + 3 + this.numParticlesPot * particleTexChannels * 2] = a2;
      var particleRate = pc.math.lerp(this.rate, this.rate2, rX);
      var startSpawnTime = -particleRate * i;
      var maxNegLife = Math.max(this.lifetime, (this.numParticles - 1) * Math.max(this.rate, this.rate2));
      var maxPosLife = this.lifetime + 1;
      startSpawnTime = (startSpawnTime + maxNegLife) / (maxNegLife + maxPosLife);
      var rgba3 = encodeFloatRGBA(startSpawnTime);
      this.particleTex[i * particleTexChannels + 0 + this.numParticlesPot * particleTexChannels * 3] = rgba3[0];
      this.particleTex[i * particleTexChannels + 1 + this.numParticlesPot * particleTexChannels * 3] = rgba3[1];
      this.particleTex[i * particleTexChannels + 2 + this.numParticlesPot * particleTexChannels * 3] = rgba3[2];
      this.particleTex[i * particleTexChannels + 3 + this.numParticlesPot * particleTexChannels * 3] = rgba3[3];
    } else {
      this.particleTex[i * particleTexChannels] = randomPosTformed.data[0];
      this.particleTex[i * particleTexChannels + 1] = randomPosTformed.data[1];
      this.particleTex[i * particleTexChannels + 2] = randomPosTformed.data[2];
      this.particleTex[i * particleTexChannels + 3] = pc.math.lerp(this.startAngle * pc.math.DEG_TO_RAD, this.startAngle2 * pc.math.DEG_TO_RAD, rX);
      var particleRate = pc.math.lerp(this.rate, this.rate2, rX);
      var startSpawnTime = -particleRate * i;
      this.particleTex[i * particleTexChannels + 3 + this.numParticlesPot * particleTexChannels] = startSpawnTime;
    }
  }, rebuildGraphs:function() {
    var precision = this.precision;
    var gd = this.graphicsDevice;
    var i;
    this.qLocalVelocity = this.localVelocityGraph.quantize(precision);
    this.qVelocity = this.velocityGraph.quantize(precision);
    this.qColor = this.colorGraph.quantize(precision);
    this.qRotSpeed = this.rotationSpeedGraph.quantize(precision);
    this.qScale = this.scaleGraph.quantize(precision);
    this.qAlpha = this.alphaGraph.quantize(precision);
    this.qLocalVelocity2 = this.localVelocityGraph2.quantize(precision);
    this.qVelocity2 = this.velocityGraph2.quantize(precision);
    this.qColor2 = this.colorGraph2.quantize(precision);
    this.qRotSpeed2 = this.rotationSpeedGraph2.quantize(precision);
    this.qScale2 = this.scaleGraph2.quantize(precision);
    this.qAlpha2 = this.alphaGraph2.quantize(precision);
    for (i = 0;i < precision;i++) {
      this.qRotSpeed[i] *= pc.math.DEG_TO_RAD;
      this.qRotSpeed2[i] *= pc.math.DEG_TO_RAD;
    }
    this.localVelocityUMax = new pc.Vec3(0, 0, 0);
    this.velocityUMax = new pc.Vec3(0, 0, 0);
    this.colorUMax = new pc.Vec3(0, 0, 0);
    this.rotSpeedUMax = [0];
    this.scaleUMax = [0];
    this.alphaUMax = [0];
    this.qLocalVelocityDiv = divGraphFrom2Curves(this.qLocalVelocity, this.qLocalVelocity2, this.localVelocityUMax.data);
    this.qVelocityDiv = divGraphFrom2Curves(this.qVelocity, this.qVelocity2, this.velocityUMax.data);
    this.qColorDiv = divGraphFrom2Curves(this.qColor, this.qColor2, this.colorUMax.data);
    this.qRotSpeedDiv = divGraphFrom2Curves(this.qRotSpeed, this.qRotSpeed2, this.rotSpeedUMax);
    this.qScaleDiv = divGraphFrom2Curves(this.qScale, this.qScale2, this.scaleUMax);
    this.qAlphaDiv = divGraphFrom2Curves(this.qAlpha, this.qAlpha2, this.alphaUMax);
    if (this.pack8) {
      var umax = [0, 0, 0];
      maxUnsignedGraphValue(this.qVelocity, umax);
      var umax2 = [0, 0, 0];
      maxUnsignedGraphValue(this.qVelocity2, umax2);
      var lumax = [0, 0, 0];
      maxUnsignedGraphValue(this.qLocalVelocity, lumax);
      var lumax2 = [0, 0, 0];
      maxUnsignedGraphValue(this.qLocalVelocity2, lumax2);
      var maxVel = Math.max(umax[0], umax2[0]);
      maxVel = Math.max(maxVel, umax[1]);
      maxVel = Math.max(maxVel, umax2[1]);
      maxVel = Math.max(maxVel, umax[2]);
      maxVel = Math.max(maxVel, umax2[2]);
      var lmaxVel = Math.max(lumax[0], lumax2[0]);
      lmaxVel = Math.max(lmaxVel, lumax[1]);
      lmaxVel = Math.max(lmaxVel, lumax2[1]);
      lmaxVel = Math.max(lmaxVel, lumax[2]);
      lmaxVel = Math.max(lmaxVel, lumax2[2]);
      this.maxVel = maxVel + lmaxVel;
    }
    if (!this.useCpu) {
      this.internalTex0 = _createTexture(gd, precision, 1, packTextureXYZ_NXYZ(this.qLocalVelocity, this.qLocalVelocityDiv));
      this.internalTex1 = _createTexture(gd, precision, 1, packTextureXYZ_NXYZ(this.qVelocity, this.qVelocityDiv));
      this.internalTex2 = _createTexture(gd, precision, 1, packTexture5Floats(this.qRotSpeed, this.qScale, this.qScaleDiv, this.qRotSpeedDiv, this.qAlphaDiv));
    }
    this.internalTex3 = _createTexture(gd, precision, 1, packTextureRGBA(this.qColor, this.qAlpha), pc.PIXELFORMAT_R8_G8_B8_A8, 1, true);
  }, _initializeTextures:function() {
    if (this.colorMap) {
      this.material.setParameter("colorMap", this.colorMap);
      if (this.lighting && this.normalMap) {
        this.material.setParameter("normalMap", this.normalMap);
      }
    }
  }, regenShader:function() {
    var programLib = this.graphicsDevice.getProgramLibrary();
    var hasNormal = this.normalMap !== null;
    this.normalOption = 0;
    if (this.lighting) {
      this.normalOption = hasNormal ? 2 : 1;
    }
    this.material.updateShader = function() {
      if (this.emitter.scene) {
        if (this.emitter.camera != this.emitter.scene._activeCamera) {
          this.emitter.camera = this.emitter.scene._activeCamera;
          this.emitter.onChangeCamera();
        }
      }
      var shader = programLib.getProgram("particle", {useCpu:this.emitter.useCpu, normal:this.emitter.normalOption, halflambert:this.emitter.halfLambert, stretch:this.emitter.stretch, alignToMotion:this.emitter.alignToMotion, soft:this.emitter.depthSoftening, mesh:this.emitter.useMesh, gamma:this.emitter.scene ? this.emitter.scene.gammaCorrection : 0, toneMap:this.emitter.scene ? this.emitter.scene.toneMapping : 0, fog:this.emitter.scene && !this.emitter.noFog ? this.emitter.scene.fog : "none", wrap:this.emitter.wrap && 
      this.emitter.wrapBounds, localSpace:this.emitter.localSpace, blend:this.blendType, animTex:this.emitter._isAnimated(), animTexLoop:this.emitter.animLoop, pack8:this.emitter.pack8});
      this.setShader(shader);
    };
    this.material.updateShader();
  }, resetMaterial:function() {
    var material = this.material;
    var gd = this.graphicsDevice;
    material.setParameter("stretch", this.stretch);
    if (this._isAnimated()) {
      material.setParameter("animTexParams", this.animParams.data);
    }
    material.setParameter("colorMult", this.intensity);
    if (!this.useCpu) {
      material.setParameter("internalTex0", this.internalTex0);
      material.setParameter("internalTex1", this.internalTex1);
      material.setParameter("internalTex2", this.internalTex2);
    }
    material.setParameter("internalTex3", this.internalTex3);
    material.setParameter("numParticles", this.numParticles);
    material.setParameter("numParticlesPot", this.numParticlesPot);
    material.setParameter("lifetime", this.lifetime);
    material.setParameter("rate", this.rate);
    material.setParameter("rateDiv", this.rate2 - this.rate);
    material.setParameter("seed", this.seed);
    material.setParameter("scaleDivMult", this.scaleUMax[0]);
    material.setParameter("alphaDivMult", this.alphaUMax[0]);
    material.setParameter("graphNumSamples", this.precision);
    material.setParameter("graphSampleSize", 1 / this.precision);
    material.setParameter("emitterScale", pc.Vec3.ONE.data);
    if (this.pack8) {
      material.setParameter("inBoundsSize", this.worldBoundsSize.data);
      material.setParameter("inBoundsCenter", this.worldBounds.center.data);
      material.setParameter("maxVel", this.maxVel);
    }
    if (this.wrap && this.wrapBounds) {
      material.setParameter("wrapBounds", this.wrapBounds.data);
    }
    if (this.colorMap) {
      material.setParameter("colorMap", this.colorMap);
    }
    if (this.lighting) {
      if (this.normalMap) {
        material.setParameter("normalMap", this.normalMap);
      }
    }
    if (this.depthSoftening > 0) {
      material.setParameter("softening", 1 / (this.depthSoftening * this.depthSoftening * 100));
    }
    if (this.stretch > 0) {
      material.cull = pc.CULLFACE_NONE;
    }
  }, _allocate:function(numParticles) {
    var psysVertCount = numParticles * this.numParticleVerts;
    var psysIndexCount = numParticles * this.numParticleIndices;
    var elements, particleFormat;
    var i;
    if (this.vertexBuffer === undefined || this.vertexBuffer.getNumVertices() !== psysVertCount) {
      if (!this.useCpu) {
        elements = [{semantic:pc.SEMANTIC_ATTR0, components:4, type:pc.ELEMENTTYPE_FLOAT32}];
        particleFormat = new pc.VertexFormat(this.graphicsDevice, elements);
        this.vertexBuffer = new pc.VertexBuffer(this.graphicsDevice, particleFormat, psysVertCount, pc.BUFFER_DYNAMIC);
        this.indexBuffer = new pc.IndexBuffer(this.graphicsDevice, pc.INDEXFORMAT_UINT16, psysIndexCount);
      } else {
        elements = [{semantic:pc.SEMANTIC_ATTR0, components:4, type:pc.ELEMENTTYPE_FLOAT32}, {semantic:pc.SEMANTIC_ATTR1, components:4, type:pc.ELEMENTTYPE_FLOAT32}, {semantic:pc.SEMANTIC_ATTR2, components:4, type:pc.ELEMENTTYPE_FLOAT32}, {semantic:pc.SEMANTIC_ATTR3, components:2, type:pc.ELEMENTTYPE_FLOAT32}];
        particleFormat = new pc.VertexFormat(this.graphicsDevice, elements);
        this.vertexBuffer = new pc.VertexBuffer(this.graphicsDevice, particleFormat, psysVertCount, pc.BUFFER_DYNAMIC);
        this.indexBuffer = new pc.IndexBuffer(this.graphicsDevice, pc.INDEXFORMAT_UINT16, psysIndexCount);
      }
      var data = new Float32Array(this.vertexBuffer.lock());
      var meshData, stride;
      if (this.useMesh) {
        meshData = new Float32Array(this.mesh.vertexBuffer.lock());
        stride = meshData.length / this.mesh.vertexBuffer.numVertices;
      }
      var id, rnd;
      for (i = 0;i < psysVertCount;i++) {
        id = Math.floor(i / this.numParticleVerts);
        if (this.useCpu) {
          if (i % this.numParticleVerts === 0) {
            rnd = this.particleTex[i * particleTexChannels + 0 + this.numParticlesPot * 2 * particleTexChannels];
          }
        }
        if (!this.useMesh) {
          var vertID = i % 4;
          data[i * 4] = particleVerts[vertID][0];
          data[i * 4 + 1] = particleVerts[vertID][1];
          data[i * 4 + 2] = 0;
        } else {
          var vert = i % this.numParticleVerts;
          data[i * 4] = meshData[vert * stride];
          data[i * 4 + 1] = meshData[vert * stride + 1];
          data[i * 4 + 2] = meshData[vert * stride + 2];
        }
        data[i * 4 + 3] = id;
      }
      if (this.useCpu) {
        this.vbCPU = new Float32Array(data);
        this.vbOld = new Float32Array(this.vbCPU.length);
      }
      this.vertexBuffer.unlock();
      if (this.useMesh) {
        this.mesh.vertexBuffer.unlock();
      }
      var dst = 0;
      var indices = new Uint16Array(this.indexBuffer.lock());
      if (this.useMesh) {
        meshData = new Uint16Array(this.mesh.indexBuffer[0].lock());
      }
      for (i = 0;i < numParticles;i++) {
        if (!this.useMesh) {
          var baseIndex = i * 4;
          indices[dst++] = baseIndex;
          indices[dst++] = baseIndex + 1;
          indices[dst++] = baseIndex + 2;
          indices[dst++] = baseIndex;
          indices[dst++] = baseIndex + 2;
          indices[dst++] = baseIndex + 3;
        } else {
          for (var j = 0;j < this.numParticleIndices;j++) {
            indices[i * this.numParticleIndices + j] = meshData[j] + i * this.numParticleVerts;
          }
        }
      }
      this.indexBuffer.unlock();
      if (this.useMesh) {
        this.mesh.indexBuffer[0].unlock();
      }
    }
  }, reset:function() {
    this.beenReset = true;
    this.seed = Math.random();
    this.material.setParameter("seed", this.seed);
    if (this.useCpu) {
      for (var i = 0;i < this.particleTexStart.length;i++) {
        this.particleTex[i] = this.particleTexStart[i];
      }
    } else {
      this._initializeTextures();
    }
    this.resetTime();
    var origLoop = this.loop;
    this.loop = true;
    this.addTime(0);
    this.loop = origLoop;
    if (this.preWarm) {
      this.prewarm(this.lifetime);
    }
  }, prewarm:function(time) {
    var lifetimeFraction = time / this.lifetime;
    var iterations = Math.min(Math.floor(lifetimeFraction * this.precision), this.precision);
    var stepDelta = time / iterations;
    for (var i = 0;i < iterations;i++) {
      this.addTime(stepDelta);
    }
  }, resetTime:function() {
    this.endTime = calcEndTime(this);
  }, onEnableDepth:function() {
    if (this.depthSoftening > 0 && this.camera) {
      this.camera.requestDepthMap();
    }
  }, onDisableDepth:function() {
    if (this.depthSoftening > 0 && this.camera) {
      this.camera.releaseDepthMap();
    }
  }, finishFrame:function() {
    if (this.useCpu) {
      this.vertexBuffer.unlock();
    }
  }, addTime:function(delta, isOnStop) {
    var i, j;
    var device = this.graphicsDevice;
    this.simTimeTotal += delta;
    this.calculateWorldBounds();
    if (this._isAnimated()) {
      var params = this.animParams;
      params.x = 1 / this.animTilesX;
      params.y = 1 / this.animTilesY;
      params.z = this.animNumFrames * this.animSpeed;
      params.w = this.animNumFrames - 1;
    }
    if (this.lighting) {
      if (!this.scene) {
        console.error("There is no scene defined for lighting particles");
        return;
      }
      for (i = 0;i < 6;i++) {
        this.lightCube[i * 3] = this.scene.ambientLight.data[0];
        this.lightCube[i * 3 + 1] = this.scene.ambientLight.data[1];
        this.lightCube[i * 3 + 2] = this.scene.ambientLight.data[2];
      }
      var dirs = this.scene._globalLights;
      for (i = 0;i < dirs.length;i++) {
        for (var c = 0;c < 6;c++) {
          var weight = Math.max(this.lightCubeDir[c].dot(dirs[i]._direction), 0) * dirs[i]._intensity;
          this.lightCube[c * 3] += dirs[i]._color.data[0] * weight;
          this.lightCube[c * 3 + 1] += dirs[i]._color.data[1] * weight;
          this.lightCube[c * 3 + 2] += dirs[i]._color.data[2] * weight;
        }
      }
      this.constantLightCube.setValue(this.lightCube);
    }
    if (this.scene) {
      if (this.camera != this.scene._activeCamera) {
        this.camera = this.scene._activeCamera;
        this.onChangeCamera();
      }
    }
    if (this.emitterShape === pc.EMITTERSHAPE_BOX) {
      if (this.meshInstance.node === null) {
        spawnMatrix.setTRS(pc.Vec3.ZERO, pc.Quat.IDENTITY, this.emitterExtents);
      } else {
        spawnMatrix.setTRS(pc.Vec3.ZERO, this.meshInstance.node.getRotation(), tmpVec3.copy(this.emitterExtents).mul(this.meshInstance.node.localScale));
      }
    }
    var emitterPos;
    var emitterScale = this.meshInstance.node === null ? pc.Vec3.ONE.data : this.meshInstance.node.localScale.data;
    this.material.setParameter("emitterScale", emitterScale);
    if (this.localSpace && this.meshInstance.node) {
      this.material.setParameter("emitterPos", this.meshInstance.node.getPosition().data);
    }
    if (!this.useCpu) {
      device.setBlending(false);
      device.setColorWrite(true, true, true, true);
      device.setCullMode(pc.CULLFACE_NONE);
      device.setDepthTest(false);
      device.setDepthWrite(false);
      this.frameRandom.x = Math.random();
      this.frameRandom.y = Math.random();
      this.frameRandom.z = Math.random();
      this.constantGraphSampleSize.setValue(1 / this.precision);
      this.constantGraphNumSamples.setValue(this.precision);
      this.constantNumParticles.setValue(this.numParticles);
      this.constantNumParticlesPot.setValue(this.numParticlesPot);
      this.constantInternalTex0.setValue(this.internalTex0);
      this.constantInternalTex1.setValue(this.internalTex1);
      this.constantInternalTex2.setValue(this.internalTex2);
      if (this.pack8) {
        this.constantOutBoundsMul.setValue(this.worldBoundsMul.data);
        this.constantOutBoundsAdd.setValue(this.worldBoundsAdd.data);
        this.constantInBoundsSize.setValue(this.prevWorldBoundsSize.data);
        this.constantInBoundsCenter.setValue(this.prevWorldBoundsCenter.data);
        var maxVel = this.maxVel * Math.max(Math.max(emitterScale[0], emitterScale[1]), emitterScale[2]);
        maxVel = Math.max(maxVel, 1);
        this.constantMaxVel.setValue(maxVel);
      }
      emitterPos = this.meshInstance.node === null || this.localSpace ? pc.Vec3.ZERO.data : this.meshInstance.node.getPosition().data;
      var emitterMatrix = this.meshInstance.node === null ? pc.Mat4.IDENTITY : this.meshInstance.node.getWorldTransform();
      if (this.emitterShape === pc.EMITTERSHAPE_BOX) {
        mat4ToMat3(spawnMatrix, spawnMatrix3);
        this.constantSpawnBounds.setValue(spawnMatrix3.data);
      } else {
        this.constantSpawnBoundsSphere.setValue(this.emitterRadius);
      }
      this.constantInitialVelocity.setValue(this.initialVelocity);
      mat4ToMat3(emitterMatrix, emitterMatrix3);
      this.constantEmitterPos.setValue(emitterPos);
      this.constantFrameRandom.setValue(this.frameRandom.data);
      this.constantDelta.setValue(delta);
      this.constantRate.setValue(this.rate);
      this.constantRateDiv.setValue(this.rate2 - this.rate);
      this.constantStartAngle.setValue(this.startAngle * pc.math.DEG_TO_RAD);
      this.constantStartAngle2.setValue(this.startAngle2 * pc.math.DEG_TO_RAD);
      this.constantSeed.setValue(this.seed);
      this.constantLifetime.setValue(this.lifetime);
      this.constantEmitterScale.setValue(emitterScale);
      this.constantEmitterMatrix.setValue(emitterMatrix3.data);
      this.constantLocalVelocityDivMult.setValue(this.localVelocityUMax.data);
      this.constantVelocityDivMult.setValue(this.velocityUMax.data);
      this.constantRotSpeedDivMult.setValue(this.rotSpeedUMax[0]);
      var texIN = this.swapTex ? this.particleTexOUT : this.particleTexIN;
      texIN = this.beenReset ? this.particleTexStart : texIN;
      var texOUT = this.swapTex ? this.particleTexIN : this.particleTexOUT;
      this.constantParticleTexIN.setValue(texIN);
      if (!isOnStop) {
        pc.drawQuadWithShader(device, this.swapTex ? this.rtParticleTexIN : this.rtParticleTexOUT, this.loop ? this.shaderParticleUpdateRespawn : this.shaderParticleUpdateNoRespawn);
      } else {
        pc.drawQuadWithShader(device, this.swapTex ? this.rtParticleTexIN : this.rtParticleTexOUT, this.shaderParticleUpdateOnStop);
      }
      this.constantParticleTexOUT.setValue(texOUT);
      this.material.setParameter("particleTexOUT", texIN);
      this.material.setParameter("particleTexIN", texOUT);
      this.beenReset = false;
      this.swapTex = !this.swapTex;
      device.setDepthTest(true);
      device.setDepthWrite(true);
    } else {
      var data = new Float32Array(this.vertexBuffer.lock());
      if (this.meshInstance.node) {
        var fullMat = this.meshInstance.node.worldTransform;
        for (j = 0;j < 12;j++) {
          rotMat.data[j] = fullMat.data[j];
        }
        nonUniformScale = this.meshInstance.node.localScale;
        uniformScale = Math.max(Math.max(nonUniformScale.x, nonUniformScale.y), nonUniformScale.z);
      }
      emitterPos = this.meshInstance.node === null || this.localSpace ? pc.Vec3.ZERO : this.meshInstance.node.getPosition();
      var posCam = this.camera ? this.camera._node.getPosition() : pc.Vec3.ZERO;
      var vertSize = 14;
      var a, b, c;
      var cf, cc;
      var rotSpeed, rotSpeed2, scale2, alpha, alpha2;
      var precision1 = this.precision - 1;
      for (i = 0;i < this.numParticles;i++) {
        var id = Math.floor(this.vbCPU[i * this.numParticleVerts * 4 + 3]);
        var rndFactor = this.particleTex[id * particleTexChannels + 0 + this.numParticlesPot * 2 * particleTexChannels];
        rndFactor3Vec.data[0] = rndFactor;
        rndFactor3Vec.data[1] = this.particleTex[id * particleTexChannels + 1 + this.numParticlesPot * 2 * particleTexChannels];
        rndFactor3Vec.data[2] = this.particleTex[id * particleTexChannels + 2 + this.numParticlesPot * 2 * particleTexChannels];
        var particleRate = this.rate + (this.rate2 - this.rate) * rndFactor;
        var particleLifetime = this.lifetime;
        var startSpawnTime = -particleRate * id;
        var life = this.particleTex[id * particleTexChannels + 3 + this.numParticlesPot * particleTexChannels] + delta;
        var nlife = saturate(life / particleLifetime);
        var scale = 0;
        var alphaDiv = 0;
        var angle = 0;
        var len;
        var interpolation;
        var particleEnabled = life > 0 && life < particleLifetime;
        if (particleEnabled) {
          c = nlife * precision1;
          cf = Math.floor(c);
          cc = Math.ceil(c);
          c = c % 1;
          a = this.qRotSpeed[cf];
          b = this.qRotSpeed[cc];
          rotSpeed = a + (b - a) * c;
          a = this.qRotSpeed2[cf];
          b = this.qRotSpeed2[cc];
          rotSpeed2 = a + (b - a) * c;
          a = this.qScale[cf];
          b = this.qScale[cc];
          scale = a + (b - a) * c;
          a = this.qScale2[cf];
          b = this.qScale2[cc];
          scale2 = a + (b - a) * c;
          a = this.qAlpha[cf];
          b = this.qAlpha[cc];
          alpha = a + (b - a) * c;
          a = this.qAlpha2[cf];
          b = this.qAlpha2[cc];
          alpha2 = a + (b - a) * c;
          cf *= 3;
          cc *= 3;
          a = this.qLocalVelocity[cf];
          b = this.qLocalVelocity[cc];
          localVelocityVec.data[0] = a + (b - a) * c;
          a = this.qLocalVelocity[cf + 1];
          b = this.qLocalVelocity[cc + 1];
          localVelocityVec.data[1] = a + (b - a) * c;
          a = this.qLocalVelocity[cf + 2];
          b = this.qLocalVelocity[cc + 2];
          localVelocityVec.data[2] = a + (b - a) * c;
          a = this.qLocalVelocity2[cf];
          b = this.qLocalVelocity2[cc];
          localVelocityVec2.data[0] = a + (b - a) * c;
          a = this.qLocalVelocity2[cf + 1];
          b = this.qLocalVelocity2[cc + 1];
          localVelocityVec2.data[1] = a + (b - a) * c;
          a = this.qLocalVelocity2[cf + 2];
          b = this.qLocalVelocity2[cc + 2];
          localVelocityVec2.data[2] = a + (b - a) * c;
          a = this.qVelocity[cf];
          b = this.qVelocity[cc];
          velocityVec.data[0] = a + (b - a) * c;
          a = this.qVelocity[cf + 1];
          b = this.qVelocity[cc + 1];
          velocityVec.data[1] = a + (b - a) * c;
          a = this.qVelocity[cf + 2];
          b = this.qVelocity[cc + 2];
          velocityVec.data[2] = a + (b - a) * c;
          a = this.qVelocity2[cf];
          b = this.qVelocity2[cc];
          velocityVec2.data[0] = a + (b - a) * c;
          a = this.qVelocity2[cf + 1];
          b = this.qVelocity2[cc + 1];
          velocityVec2.data[1] = a + (b - a) * c;
          a = this.qVelocity2[cf + 2];
          b = this.qVelocity2[cc + 2];
          velocityVec2.data[2] = a + (b - a) * c;
          localVelocityVec.data[0] = localVelocityVec.data[0] + (localVelocityVec2.data[0] - localVelocityVec.data[0]) * rndFactor3Vec.data[0];
          localVelocityVec.data[1] = localVelocityVec.data[1] + (localVelocityVec2.data[1] - localVelocityVec.data[1]) * rndFactor3Vec.data[1];
          localVelocityVec.data[2] = localVelocityVec.data[2] + (localVelocityVec2.data[2] - localVelocityVec.data[2]) * rndFactor3Vec.data[2];
          if (this.initialVelocity > 0) {
            if (this.emitterShape === pc.EMITTERSHAPE_SPHERE) {
              randomPos.copy(rndFactor3Vec).scale(2).sub(pc.Vec3.ONE).normalize();
              localVelocityVec.add(randomPos.scale(this.initialVelocity));
            } else {
              localVelocityVec.add(pc.Vec3.FORWARD.scale(this.initialVelocity));
            }
          }
          velocityVec.data[0] = velocityVec.data[0] + (velocityVec2.data[0] - velocityVec.data[0]) * rndFactor3Vec.data[0];
          velocityVec.data[1] = velocityVec.data[1] + (velocityVec2.data[1] - velocityVec.data[1]) * rndFactor3Vec.data[1];
          velocityVec.data[2] = velocityVec.data[2] + (velocityVec2.data[2] - velocityVec.data[2]) * rndFactor3Vec.data[2];
          rotSpeed = rotSpeed + (rotSpeed2 - rotSpeed) * rndFactor3Vec.data[1];
          scale = (scale + (scale2 - scale) * (rndFactor * 1E4 % 1)) * uniformScale;
          alphaDiv = (alpha2 - alpha) * (rndFactor * 1E3 % 1);
          if (this.meshInstance.node) {
            rotMat.transformPoint(localVelocityVec, localVelocityVec);
          }
          localVelocityVec.add(velocityVec.mul(nonUniformScale));
          moveDirVec.copy(localVelocityVec);
          particlePosPrev.data[0] = this.particleTex[id * particleTexChannels];
          particlePosPrev.data[1] = this.particleTex[id * particleTexChannels + 1];
          particlePosPrev.data[2] = this.particleTex[id * particleTexChannels + 2];
          particlePos.copy(particlePosPrev).add(localVelocityVec.scale(delta));
          particleFinalPos.copy(particlePos);
          this.particleTex[id * particleTexChannels] = particleFinalPos.data[0];
          this.particleTex[id * particleTexChannels + 1] = particleFinalPos.data[1];
          this.particleTex[id * particleTexChannels + 2] = particleFinalPos.data[2];
          this.particleTex[id * particleTexChannels + 3] += rotSpeed * delta;
          if (this.wrap && this.wrapBounds) {
            particleFinalPos.sub(emitterPos);
            particleFinalPos.data[0] = glMod(particleFinalPos.data[0], this.wrapBounds.data[0]) - this.wrapBounds.data[0] * .5;
            particleFinalPos.data[1] = glMod(particleFinalPos.data[1], this.wrapBounds.data[1]) - this.wrapBounds.data[1] * .5;
            particleFinalPos.data[2] = glMod(particleFinalPos.data[2], this.wrapBounds.data[2]) - this.wrapBounds.data[2] * .5;
            particleFinalPos.add(emitterPos);
          }
          if (this.sort > 0) {
            if (this.sort === 1) {
              tmpVec3.copy(particleFinalPos).sub(posCam);
              this.particleDistance[id] = -(tmpVec3.data[0] * tmpVec3.data[0] + tmpVec3.data[1] * tmpVec3.data[1] + tmpVec3.data[2] * tmpVec3.data[2]);
            } else {
              if (this.sort === 2) {
                this.particleDistance[id] = life;
              } else {
                if (this.sort === 3) {
                  this.particleDistance[id] = -life;
                }
              }
            }
          }
        } else {
          this.calcSpawnPosition(emitterPos, id);
        }
        if (isOnStop) {
          if (life < 0) {
            this.particleTex[id * particleTexChannels + 3 + this.numParticlesPot * 2 * particleTexChannels] = -1;
          }
        } else {
          if (life >= particleLifetime) {
            life -= Math.max(particleLifetime, (this.numParticles - 1) * particleRate);
            this.particleTex[id * particleTexChannels + 3 + this.numParticlesPot * 2 * particleTexChannels] = this.loop ? 1 : -1;
          }
          if (life < 0 && this.loop) {
            this.particleTex[id * particleTexChannels + 3 + this.numParticlesPot * 2 * particleTexChannels] = 1;
          }
        }
        if (this.particleTex[id * particleTexChannels + 3 + this.numParticlesPot * 2 * particleTexChannels] < 0) {
          particleEnabled = false;
        }
        this.particleTex[id * particleTexChannels + 3 + this.numParticlesPot * particleTexChannels] = life;
        for (var v = 0;v < this.numParticleVerts;v++) {
          var quadX = this.vbCPU[i * this.numParticleVerts * 4 + v * 4];
          var quadY = this.vbCPU[i * this.numParticleVerts * 4 + v * 4 + 1];
          var quadZ = this.vbCPU[i * this.numParticleVerts * 4 + v * 4 + 2];
          if (!particleEnabled) {
            quadX = quadY = quadZ = 0;
          }
          var w = i * this.numParticleVerts * vertSize + v * vertSize;
          data[w] = particleFinalPos.data[0];
          data[w + 1] = particleFinalPos.data[1];
          data[w + 2] = particleFinalPos.data[2];
          data[w + 3] = nlife;
          data[w + 4] = this.alignToMotion ? angle : this.particleTex[id * particleTexChannels + 3];
          data[w + 5] = scale;
          data[w + 6] = alphaDiv;
          data[w + 7] = moveDirVec.data[0];
          data[w + 8] = quadX;
          data[w + 9] = quadY;
          data[w + 10] = quadZ;
          data[w + 11] = moveDirVec.data[1];
          data[w + 12] = moveDirVec.data[2];
        }
      }
      if (this.sort > pc.PARTICLESORT_NONE && this.camera) {
        for (i = 0;i < this.numParticles;i++) {
          this.vbToSort[i] = [i, Math.floor(this.vbCPU[i * this.numParticleVerts * 4 + 3])];
        }
        for (i = 0;i < this.vbCPU.length;i++) {
          this.vbOld[i] = this.vbCPU[i];
        }
        var particleDistance = this.particleDistance;
        this.vbToSort.sort(function(a, b) {
          return particleDistance[a[1]] - particleDistance[b[1]];
        });
        for (i = 0;i < this.numParticles;i++) {
          var start = this.vbToSort[i][0];
          for (var corner = 0;corner < this.numParticleVerts;corner++) {
            for (j = 0;j < 4;j++) {
              this.vbCPU[i * this.numParticleVerts * 4 + corner * 4 + j] = this.vbOld[start * this.numParticleVerts * 4 + corner * 4 + j];
            }
          }
        }
      }
    }
    if (!this.loop) {
      if (Date.now() > this.endTime) {
        if (this.onFinished) {
          this.onFinished();
        }
        this.meshInstance.visible = false;
      }
    }
  }, destroy:function() {
    if (this.particleTexIN) {
      this.particleTexIN.destroy();
    }
    if (this.particleTexOUT) {
      this.particleTexOUT.destroy();
    }
    if (!this.useCpu && this.particleTexStart) {
      this.particleTexStart.destroy();
    }
    if (this.rtParticleTexIN) {
      this.rtParticleTexIN.destroy();
    }
    if (this.rtParticleTexOUT) {
      this.rtParticleTexOUT.destroy();
    }
    this.particleTexIN = null;
    this.particleTexOUT = null;
    this.particleTexStart = null;
    this.rtParticleTexIN = null;
    this.rtParticleTexOUT = null;
    this.shaderParticleUpdateRespawn = null;
    this.shaderParticleUpdateNoRespawn = null;
    this.shaderParticleUpdateOnStop = null;
  }};
  return {ParticleEmitter:ParticleEmitter};
}());
function frac(f) {
  return f - Math.floor(f);
}
function encodeFloatRGBA(v) {
  var encX = frac(v);
  var encY = frac(255 * v);
  var encZ = frac(65025 * v);
  var encW = frac(160581375 * v);
  encX -= encY / 255;
  encY -= encZ / 255;
  encZ -= encW / 255;
  encW -= encW / 255;
  return [encX, encY, encZ, encW];
}
function encodeFloatRG(v) {
  var encX = frac(v);
  var encY = frac(255 * v);
  encX -= encY / 255;
  encY -= encY / 255;
  return [encX, encY];
}
;pc.extend(pc, function() {
  function sortDrawCalls(drawCallA, drawCallB) {
    return drawCallB.key - drawCallA.key;
  }
  var Picker = function(device, width, height) {
    this.device = device;
    this.library = device.getProgramLibrary();
    this.pickColor = new Float32Array(4);
    this.scene = null;
    this.drawCalls = [];
    this.clearOptions = {color:[1, 1, 1, 1], depth:1, flags:pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH};
    this.resize(width, height);
    this._ignoreOpacityFor = null;
  };
  Picker.prototype.getSelection = function(rect) {
    var device = this.device;
    rect.width = rect.width || 1;
    rect.height = rect.height || 1;
    var prevRenderTarget = device.renderTarget;
    device.setRenderTarget(this._pickBufferTarget);
    device.updateBegin();
    var pixels = new Uint8Array(4 * rect.width * rect.height);
    device.readPixels(rect.x, rect.y, rect.width, rect.height, pixels);
    device.updateEnd();
    device.setRenderTarget(prevRenderTarget);
    var selection = [];
    for (var i = 0;i < rect.width * rect.height;i++) {
      var r = pixels[4 * i + 0];
      var g = pixels[4 * i + 1];
      var b = pixels[4 * i + 2];
      var index = r << 16 | g << 8 | b;
      if (index !== 16777215) {
        var selectedMeshInstance = this.drawCalls[index];
        if (selection.indexOf(selectedMeshInstance) === -1) {
          selection.push(selectedMeshInstance);
        }
      }
    }
    return selection;
  };
  Picker.prototype.prepare = function(camera, scene) {
    var device = this.device;
    this.scene = scene;
    var prevRenderTarget = device.renderTarget;
    device.setRenderTarget(this._pickBufferTarget);
    device.updateBegin();
    device.setViewport(0, 0, this._pickBufferTarget.width, this._pickBufferTarget.height);
    device.setScissor(0, 0, this._pickBufferTarget.width, this._pickBufferTarget.height);
    device.clear(this.clearOptions);
    var i;
    var mesh, meshInstance, material;
    var type;
    var shader, isLastSelected;
    var scope = device.scope;
    var modelMatrixId = scope.resolve("matrix_model");
    var boneTextureId = scope.resolve("texture_poseMap");
    var boneTextureSizeId = scope.resolve("texture_poseMapSize");
    var skinPosOffsetId = scope.resolve("skinPosOffset");
    var poseMatrixId = scope.resolve("matrix_pose[0]");
    var pickColorId = scope.resolve("uColor");
    var projId = scope.resolve("matrix_projection");
    var viewProjId = scope.resolve("matrix_viewProjection");
    var opacityMapId = scope.resolve("texture_opacityMap");
    var alphaTestId = scope.resolve("alpha_ref");
    var wtm = camera._node.getWorldTransform();
    var projMat = camera.getProjectionMatrix();
    var viewMat = wtm.clone().invert();
    var viewProjMat = new pc.Mat4;
    viewProjMat.mul2(projMat, viewMat);
    projId.setValue(projMat.data);
    viewProjId.setValue(viewProjMat.data);
    this.drawCalls = scene.drawCalls.slice(0);
    this.drawCalls.sort(sortDrawCalls);
    for (i = 0;i < this.drawCalls.length;i++) {
      if (this.drawCalls[i].command) {
        this.drawCalls[i].command();
      } else {
        if (!this.drawCalls[i].pick) {
          continue;
        }
        meshInstance = this.drawCalls[i];
        mesh = meshInstance.mesh;
        material = meshInstance.material;
        type = mesh.primitive[pc.RENDERSTYLE_SOLID].type;
        var isSolid = type === pc.PRIMITIVE_TRIANGLES || type === pc.PRIMITIVE_TRISTRIP || type === pc.PRIMITIVE_TRIFAN;
        var isPickable = material instanceof pc.StandardMaterial || material instanceof pc.BasicMaterial;
        if (isSolid && isPickable) {
          device.setBlending(false);
          device.setCullMode(material.cull);
          device.setDepthWrite(material.depthWrite);
          device.setDepthTest(material.depthTest);
          modelMatrixId.setValue(meshInstance.node.worldTransform.data);
          if (meshInstance.skinInstance) {
            skinPosOffsetId.setValue(meshInstance.node.getPosition().data);
            if (device.supportsBoneTextures) {
              boneTextureId.setValue(meshInstance.skinInstance.boneTexture);
              var w = meshInstance.skinInstance.boneTexture.width;
              var h = meshInstance.skinInstance.boneTexture.height;
              boneTextureSizeId.setValue([w, h]);
            } else {
              poseMatrixId.setValue(meshInstance.skinInstance.matrixPalette);
            }
          }
          if (material.opacityMap) {
            opacityMapId.setValue(material.opacityMap);
            alphaTestId.setValue(meshInstance === this._ignoreOpacityFor ? 0 : material.alphaTest);
          }
          this.pickColor[0] = (i >> 16 & 255) / 255;
          this.pickColor[1] = (i >> 8 & 255) / 255;
          this.pickColor[2] = (i & 255) / 255;
          this.pickColor[3] = 1;
          pickColorId.setValue(this.pickColor);
          shader = meshInstance._shader[pc.SHADER_PICK];
          if (!shader) {
            shader = this.library.getProgram("pick", {skin:!!meshInstance.skinInstance, screenSpace:meshInstance.screenSpace, opacityMap:!!material.opacityMap, opacityChannel:material.opacityMap ? material.opacityMapChannel || "r" : null});
            meshInstance._shader[pc.SHADER_PICK] = shader;
          }
          device.setShader(shader);
          device.setVertexBuffer(mesh.vertexBuffer, 0);
          device.setIndexBuffer(mesh.indexBuffer[pc.RENDERSTYLE_SOLID]);
          device.draw(mesh.primitive[pc.RENDERSTYLE_SOLID]);
        }
      }
    }
    device.setViewport(0, 0, device.width, device.height);
    device.setScissor(0, 0, device.width, device.height);
    device.updateEnd();
    device.setRenderTarget(prevRenderTarget);
  };
  Picker.prototype.resize = function(width, height) {
    var colorBuffer = new pc.Texture(this.device, {format:pc.PIXELFORMAT_R8_G8_B8_A8, width:width, height:height, mipmaps:false, minFilter:pc.FILTER_NEAREST, magFilter:pc.FILTER_NEAREST});
    this._pickBufferTarget = new pc.RenderTarget(this.device, colorBuffer, {depth:true});
  };
  Object.defineProperty(Picker.prototype, "renderTarget", {get:function() {
    return this._pickBufferTarget;
  }});
  Object.defineProperty(Picker.prototype, "width", {get:function() {
    return this._pickBufferTarget.width;
  }});
  Object.defineProperty(Picker.prototype, "height", {get:function() {
    return this._pickBufferTarget.height;
  }});
  return {Picker:Picker};
}());
var primitiveUv1Padding = 4 / 64;
var primitiveUv1PaddingScale = 1 - primitiveUv1Padding * 2;
pc.calculateNormals = function(positions, indices) {
  var triangleCount = indices.length / 3;
  var vertexCount = positions.length / 3;
  var i1, i2, i3;
  var i;
  var p1 = new pc.Vec3;
  var p2 = new pc.Vec3;
  var p3 = new pc.Vec3;
  var p1p2 = new pc.Vec3;
  var p1p3 = new pc.Vec3;
  var faceNormal = new pc.Vec3;
  var vertexNormal = new pc.Vec3;
  var normals = [];
  for (i = 0;i < positions.length;i++) {
    normals[i] = 0;
  }
  for (i = 0;i < triangleCount;i++) {
    i1 = indices[i * 3];
    i2 = indices[i * 3 + 1];
    i3 = indices[i * 3 + 2];
    p1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
    p2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
    p3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);
    p1p2.sub2(p2, p1);
    p1p3.sub2(p3, p1);
    faceNormal.cross(p1p2, p1p3).normalize();
    normals[i1 * 3] += faceNormal.x;
    normals[i1 * 3 + 1] += faceNormal.y;
    normals[i1 * 3 + 2] += faceNormal.z;
    normals[i2 * 3] += faceNormal.x;
    normals[i2 * 3 + 1] += faceNormal.y;
    normals[i2 * 3 + 2] += faceNormal.z;
    normals[i3 * 3] += faceNormal.x;
    normals[i3 * 3 + 1] += faceNormal.y;
    normals[i3 * 3 + 2] += faceNormal.z;
  }
  for (i = 0;i < vertexCount;i++) {
    var nx = normals[i * 3];
    var ny = normals[i * 3 + 1];
    var nz = normals[i * 3 + 2];
    var invLen = 1 / Math.sqrt(nx * nx + ny * ny + nz * nz);
    normals[i * 3] *= invLen;
    normals[i * 3 + 1] *= invLen;
    normals[i * 3 + 2] *= invLen;
  }
  return normals;
};
pc.calculateTangents = function(positions, normals, uvs, indices) {
  var triangleCount = indices.length / 3;
  var vertexCount = positions.length / 3;
  var i1, i2, i3;
  var x1, x2, y1, y2, z1, z2, s1, s2, t1, t2, r;
  var sdir = new pc.Vec3;
  var tdir = new pc.Vec3;
  var v1 = new pc.Vec3;
  var v2 = new pc.Vec3;
  var v3 = new pc.Vec3;
  var w1 = new pc.Vec2;
  var w2 = new pc.Vec2;
  var w3 = new pc.Vec2;
  var i;
  var tan1 = new Float32Array(vertexCount * 3);
  var tan2 = new Float32Array(vertexCount * 3);
  var tangents = [];
  for (i = 0;i < triangleCount;i++) {
    i1 = indices[i * 3];
    i2 = indices[i * 3 + 1];
    i3 = indices[i * 3 + 2];
    v1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
    v2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
    v3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);
    w1.set(uvs[i1 * 2], uvs[i1 * 2 + 1]);
    w2.set(uvs[i2 * 2], uvs[i2 * 2 + 1]);
    w3.set(uvs[i3 * 2], uvs[i3 * 2 + 1]);
    x1 = v2.x - v1.x;
    x2 = v3.x - v1.x;
    y1 = v2.y - v1.y;
    y2 = v3.y - v1.y;
    z1 = v2.z - v1.z;
    z2 = v3.z - v1.z;
    s1 = w2.x - w1.x;
    s2 = w3.x - w1.x;
    t1 = w2.y - w1.y;
    t2 = w3.y - w1.y;
    r = 1 / (s1 * t2 - s2 * t1);
    sdir.set((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
    tdir.set((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
    tan1[i1 * 3 + 0] += sdir.x;
    tan1[i1 * 3 + 1] += sdir.y;
    tan1[i1 * 3 + 2] += sdir.z;
    tan1[i2 * 3 + 0] += sdir.x;
    tan1[i2 * 3 + 1] += sdir.y;
    tan1[i2 * 3 + 2] += sdir.z;
    tan1[i3 * 3 + 0] += sdir.x;
    tan1[i3 * 3 + 1] += sdir.y;
    tan1[i3 * 3 + 2] += sdir.z;
    tan2[i1 * 3 + 0] += tdir.x;
    tan2[i1 * 3 + 1] += tdir.y;
    tan2[i1 * 3 + 2] += tdir.z;
    tan2[i2 * 3 + 0] += tdir.x;
    tan2[i2 * 3 + 1] += tdir.y;
    tan2[i2 * 3 + 2] += tdir.z;
    tan2[i3 * 3 + 0] += tdir.x;
    tan2[i3 * 3 + 1] += tdir.y;
    tan2[i3 * 3 + 2] += tdir.z;
  }
  t1 = new pc.Vec3;
  t2 = new pc.Vec3;
  var n = new pc.Vec3;
  var temp = new pc.Vec3;
  for (i = 0;i < vertexCount;i++) {
    n.set(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);
    t1.set(tan1[i * 3], tan1[i * 3 + 1], tan1[i * 3 + 2]);
    t2.set(tan2[i * 3], tan2[i * 3 + 1], tan2[i * 3 + 2]);
    var ndott = n.dot(t1);
    temp.copy(n).scale(ndott);
    temp.sub2(t1, temp).normalize();
    tangents[i * 4] = temp.x;
    tangents[i * 4 + 1] = temp.y;
    tangents[i * 4 + 2] = temp.z;
    temp.cross(n, t1);
    tangents[i * 4 + 3] = temp.dot(t2) < 0 ? -1 : 1;
  }
  return tangents;
};
pc.createMesh = function(device, positions, opts) {
  var normals = opts && opts.normals !== undefined ? opts.normals : null;
  var tangents = opts && opts.tangents !== undefined ? opts.tangents : null;
  var colors = opts && opts.colors !== undefined ? opts.colors : null;
  var uvs = opts && opts.uvs !== undefined ? opts.uvs : null;
  var uvs1 = opts && opts.uvs1 !== undefined ? opts.uvs1 : null;
  var indices = opts && opts.indices !== undefined ? opts.indices : null;
  var blendIndices = opts && opts.blendIndices !== undefined ? opts.blendIndices : null;
  var blendWeights = opts && opts.blendWeights !== undefined ? opts.blendWeights : null;
  var vertexDesc = [{semantic:pc.SEMANTIC_POSITION, components:3, type:pc.ELEMENTTYPE_FLOAT32}];
  if (normals !== null) {
    vertexDesc.push({semantic:pc.SEMANTIC_NORMAL, components:3, type:pc.ELEMENTTYPE_FLOAT32});
  }
  if (tangents !== null) {
    vertexDesc.push({semantic:pc.SEMANTIC_TANGENT, components:4, type:pc.ELEMENTTYPE_FLOAT32});
  }
  if (colors !== null) {
    vertexDesc.push({semantic:pc.SEMANTIC_COLOR, components:4, type:pc.ELEMENTTYPE_UINT8, normalize:true});
  }
  if (uvs !== null) {
    vertexDesc.push({semantic:pc.SEMANTIC_TEXCOORD0, components:2, type:pc.ELEMENTTYPE_FLOAT32});
  }
  if (uvs1 !== null) {
    vertexDesc.push({semantic:pc.SEMANTIC_TEXCOORD1, components:2, type:pc.ELEMENTTYPE_FLOAT32});
  }
  if (blendIndices !== null) {
    vertexDesc.push({semantic:pc.SEMANTIC_BLENDINDICES, components:2, type:pc.ELEMENTTYPE_UINT8});
  }
  if (blendWeights !== null) {
    vertexDesc.push({semantic:pc.SEMANTIC_BLENDWEIGHT, components:2, type:pc.ELEMENTTYPE_FLOAT32});
  }
  var vertexFormat = new pc.VertexFormat(device, vertexDesc);
  var numVertices = positions.length / 3;
  var vertexBuffer = new pc.VertexBuffer(device, vertexFormat, numVertices);
  var iterator = new pc.VertexIterator(vertexBuffer);
  for (var i = 0;i < numVertices;i++) {
    iterator.element[pc.SEMANTIC_POSITION].set(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
    if (normals !== null) {
      iterator.element[pc.SEMANTIC_NORMAL].set(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);
    }
    if (tangents !== null) {
      iterator.element[pc.SEMANTIC_TANGENT].set(tangents[i * 4], tangents[i * 4 + 1], tangents[i * 4 + 2], tangents[i * 4 + 3]);
    }
    if (colors !== null) {
      iterator.element[pc.SEMANTIC_COLOR].set(colors[i * 4], colors[i * 4 + 1], colors[i * 4 + 2], colors[i * 4 + 3]);
    }
    if (uvs !== null) {
      iterator.element[pc.SEMANTIC_TEXCOORD0].set(uvs[i * 2], uvs[i * 2 + 1]);
    }
    if (uvs1 !== null) {
      iterator.element[pc.SEMANTIC_TEXCOORD1].set(uvs1[i * 2], uvs1[i * 2 + 1]);
    }
    if (blendIndices !== null) {
      iterator.element[pc.SEMANTIC_BLENDINDICES].set(blendIndices[i * 2], blendIndices[i * 2 + 1]);
    }
    if (blendWeights !== null) {
      iterator.element[pc.SEMANTIC_BLENDWEIGHT].set(blendWeights[i * 2], blendWeights[i * 2 + 1]);
    }
    iterator.next();
  }
  iterator.end();
  var indexBuffer = null;
  var indexed = indices !== null;
  if (indexed) {
    indexBuffer = new pc.IndexBuffer(device, pc.INDEXFORMAT_UINT16, indices.length);
    var dst = new Uint16Array(indexBuffer.lock());
    dst.set(indices);
    indexBuffer.unlock();
  }
  var aabb = new pc.BoundingBox;
  aabb.compute(positions);
  var mesh = new pc.Mesh;
  mesh.vertexBuffer = vertexBuffer;
  mesh.indexBuffer[0] = indexBuffer;
  mesh.primitive[0].type = pc.PRIMITIVE_TRIANGLES;
  mesh.primitive[0].base = 0;
  mesh.primitive[0].count = indexed ? indices.length : numVertices;
  mesh.primitive[0].indexed = indexed;
  mesh.aabb = aabb;
  return mesh;
};
pc.createTorus = function(device, opts) {
  var rc = opts && opts.tubeRadius !== undefined ? opts.tubeRadius : .2;
  var rt = opts && opts.ringRadius !== undefined ? opts.ringRadius : .3;
  var segments = opts && opts.segments !== undefined ? opts.segments : 30;
  var sides = opts && opts.sides !== undefined ? opts.sides : 20;
  var i, j;
  var x, y, z, nx, ny, nz, u, v;
  var positions = [];
  var normals = [];
  var uvs = [];
  var indices = [];
  for (i = 0;i <= sides;i++) {
    for (j = 0;j <= segments;j++) {
      x = Math.cos(2 * Math.PI * j / segments) * (rt + rc * Math.cos(2 * Math.PI * i / sides));
      y = Math.sin(2 * Math.PI * i / sides) * rc;
      z = Math.sin(2 * Math.PI * j / segments) * (rt + rc * Math.cos(2 * Math.PI * i / sides));
      nx = Math.cos(2 * Math.PI * j / segments) * Math.cos(2 * Math.PI * i / sides);
      ny = Math.sin(2 * Math.PI * i / sides);
      nz = Math.sin(2 * Math.PI * j / segments) * Math.cos(2 * Math.PI * i / sides);
      u = i / sides;
      v = 1 - j / segments;
      positions.push(x, y, z);
      normals.push(nx, ny, nz);
      uvs.push(u, v);
      if (i < sides && j < segments) {
        var first, second, third, fourth;
        first = i * (segments + 1) + j;
        second = (i + 1) * (segments + 1) + j;
        third = i * (segments + 1) + (j + 1);
        fourth = (i + 1) * (segments + 1) + (j + 1);
        indices.push(first, second, third);
        indices.push(second, fourth, third);
      }
    }
  }
  var options = {normals:normals, uvs:uvs, indices:indices};
  if (pc.precalculatedTangents) {
    options.tangents = pc.calculateTangents(positions, normals, uvs, indices);
  }
  return pc.createMesh(device, positions, options);
};
pc._createConeData = function(baseRadius, peakRadius, height, heightSegments, capSegments, roundedCaps) {
  var i, j;
  var x, y, z, u, v;
  var pos = new pc.Vec3;
  var bottomToTop = new pc.Vec3;
  var norm = new pc.Vec3;
  var top, bottom, tangent;
  var positions = [];
  var normals = [];
  var uvs = [];
  var uvs1 = [];
  var indices = [];
  var theta, cosTheta, sinTheta;
  var phi, sinPhi, cosPhi;
  var first, second, third, fourth;
  var offset;
  if (height > 0) {
    for (i = 0;i <= heightSegments;i++) {
      for (j = 0;j <= capSegments;j++) {
        theta = j / capSegments * 2 * Math.PI - Math.PI;
        sinTheta = Math.sin(theta);
        cosTheta = Math.cos(theta);
        bottom = new pc.Vec3(sinTheta * baseRadius, -height / 2, cosTheta * baseRadius);
        top = new pc.Vec3(sinTheta * peakRadius, height / 2, cosTheta * peakRadius);
        pos.lerp(bottom, top, i / heightSegments);
        bottomToTop.sub2(top, bottom).normalize();
        tangent = new pc.Vec3(cosTheta, 0, -sinTheta);
        norm.cross(tangent, bottomToTop).normalize();
        positions.push(pos.x, pos.y, pos.z);
        normals.push(norm.x, norm.y, norm.z);
        u = j / capSegments;
        v = i / heightSegments;
        uvs.push(u, v);
        var _v = v;
        v = u;
        u = _v;
        u /= 3;
        u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
        v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
        uvs1.push(u, v);
        if (i < heightSegments && j < capSegments) {
          first = i * (capSegments + 1) + j;
          second = i * (capSegments + 1) + (j + 1);
          third = (i + 1) * (capSegments + 1) + j;
          fourth = (i + 1) * (capSegments + 1) + (j + 1);
          indices.push(first, second, third);
          indices.push(second, fourth, third);
        }
      }
    }
  }
  if (roundedCaps) {
    var lat, lon;
    var latitudeBands = Math.floor(capSegments / 2);
    var longitudeBands = capSegments;
    var capOffset = height / 2;
    for (lat = 0;lat <= latitudeBands;lat++) {
      theta = lat * Math.PI * .5 / latitudeBands;
      sinTheta = Math.sin(theta);
      cosTheta = Math.cos(theta);
      for (lon = 0;lon <= longitudeBands;lon++) {
        phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;
        sinPhi = Math.sin(phi);
        cosPhi = Math.cos(phi);
        x = cosPhi * sinTheta;
        y = cosTheta;
        z = sinPhi * sinTheta;
        u = 1 - lon / longitudeBands;
        v = 1 - lat / latitudeBands;
        positions.push(x * peakRadius, y * peakRadius + capOffset, z * peakRadius);
        normals.push(x, y, z);
        uvs.push(u, v);
        u /= 3;
        v /= 3;
        u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
        v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
        u += 1 / 3;
        uvs1.push(u, v);
      }
    }
    offset = (heightSegments + 1) * (capSegments + 1);
    for (lat = 0;lat < latitudeBands;++lat) {
      for (lon = 0;lon < longitudeBands;++lon) {
        first = lat * (longitudeBands + 1) + lon;
        second = first + longitudeBands + 1;
        indices.push(offset + first + 1, offset + second, offset + first);
        indices.push(offset + first + 1, offset + second + 1, offset + second);
      }
    }
    for (lat = 0;lat <= latitudeBands;lat++) {
      theta = Math.PI * .5 + lat * Math.PI * .5 / latitudeBands;
      sinTheta = Math.sin(theta);
      cosTheta = Math.cos(theta);
      for (lon = 0;lon <= longitudeBands;lon++) {
        phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;
        sinPhi = Math.sin(phi);
        cosPhi = Math.cos(phi);
        x = cosPhi * sinTheta;
        y = cosTheta;
        z = sinPhi * sinTheta;
        u = 1 - lon / longitudeBands;
        v = 1 - lat / latitudeBands;
        positions.push(x * peakRadius, y * peakRadius - capOffset, z * peakRadius);
        normals.push(x, y, z);
        uvs.push(u, v);
        u /= 3;
        v /= 3;
        u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
        v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
        u += 2 / 3;
        uvs1.push(u, v);
      }
    }
    offset = (heightSegments + 1) * (capSegments + 1) + (longitudeBands + 1) * (latitudeBands + 1);
    for (lat = 0;lat < latitudeBands;++lat) {
      for (lon = 0;lon < longitudeBands;++lon) {
        first = lat * (longitudeBands + 1) + lon;
        second = first + longitudeBands + 1;
        indices.push(offset + first + 1, offset + second, offset + first);
        indices.push(offset + first + 1, offset + second + 1, offset + second);
      }
    }
  } else {
    offset = (heightSegments + 1) * (capSegments + 1);
    if (baseRadius > 0) {
      for (i = 0;i < capSegments;i++) {
        theta = i / capSegments * 2 * Math.PI;
        x = Math.sin(theta);
        y = -height / 2;
        z = Math.cos(theta);
        u = 1 - (x + 1) / 2;
        v = (z + 1) / 2;
        positions.push(x * baseRadius, y, z * baseRadius);
        normals.push(0, -1, 0);
        uvs.push(u, v);
        u /= 3;
        v /= 3;
        u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
        v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
        u += 1 / 3;
        uvs1.push(u, v);
        if (i > 1) {
          indices.push(offset, offset + i, offset + i - 1);
        }
      }
    }
    offset += capSegments;
    if (peakRadius > 0) {
      for (i = 0;i < capSegments;i++) {
        theta = i / capSegments * 2 * Math.PI;
        x = Math.sin(theta);
        y = height / 2;
        z = Math.cos(theta);
        u = 1 - (x + 1) / 2;
        v = (z + 1) / 2;
        positions.push(x * peakRadius, y, z * peakRadius);
        normals.push(0, 1, 0);
        uvs.push(u, v);
        u /= 3;
        v /= 3;
        u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
        v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
        u += 2 / 3;
        uvs1.push(u, v);
        if (i > 1) {
          indices.push(offset, offset + i - 1, offset + i);
        }
      }
    }
  }
  return {positions:positions, normals:normals, uvs:uvs, uvs1:uvs1, indices:indices};
};
pc.createCylinder = function(device, opts) {
  var baseRadius = opts && opts.baseRadius !== undefined ? opts.baseRadius : .5;
  var height = opts && opts.height !== undefined ? opts.height : 1;
  var heightSegments = opts && opts.heightSegments !== undefined ? opts.heightSegments : 5;
  var capSegments = opts && opts.capSegments !== undefined ? opts.capSegments : 20;
  var options = pc._createConeData(baseRadius, baseRadius, height, heightSegments, capSegments, false);
  if (pc.precalculatedTangents) {
    options.tangents = pc.calculateTangents(options.positions, options.normals, options.uvs, options.indices);
  }
  return pc.createMesh(device, options.positions, options);
};
pc.createCapsule = function(device, opts) {
  var radius = opts && opts.radius !== undefined ? opts.radius : .3;
  var height = opts && opts.height !== undefined ? opts.height : 1;
  var heightSegments = opts && opts.heightSegments !== undefined ? opts.heightSegments : 1;
  var sides = opts && opts.sides !== undefined ? opts.sides : 20;
  var options = pc._createConeData(radius, radius, height - 2 * radius, heightSegments, sides, true);
  if (pc.precalculatedTangents) {
    options.tangents = pc.calculateTangents(options.positions, options.normals, options.uvs, options.indices);
  }
  return pc.createMesh(device, options.positions, options);
};
pc.createCone = function(device, opts) {
  var baseRadius = opts && opts.baseRadius !== undefined ? opts.baseRadius : .5;
  var peakRadius = opts && opts.peakRadius !== undefined ? opts.peakRadius : 0;
  var height = opts && opts.height !== undefined ? opts.height : 1;
  var heightSegments = opts && opts.heightSegments !== undefined ? opts.heightSegments : 5;
  var capSegments = opts && opts.capSegments !== undefined ? opts.capSegments : 18;
  var options = pc._createConeData(baseRadius, peakRadius, height, heightSegments, capSegments, false);
  if (pc.precalculatedTangents) {
    options.tangents = pc.calculateTangents(options.positions, options.normals, options.uvs, options.indices);
  }
  return pc.createMesh(device, options.positions, options);
};
pc.createSphere = function(device, opts) {
  var radius = opts && opts.radius !== undefined ? opts.radius : .5;
  var latitudeBands = opts && opts.latitudeBands !== undefined ? opts.latitudeBands : 16;
  var longitudeBands = opts && opts.longitudeBands !== undefined ? opts.longitudeBands : 16;
  var lon, lat;
  var theta, sinTheta, cosTheta, phi, sinPhi, cosPhi;
  var first, second;
  var x, y, z, u, v;
  var positions = [];
  var normals = [];
  var uvs = [];
  var indices = [];
  for (lat = 0;lat <= latitudeBands;lat++) {
    theta = lat * Math.PI / latitudeBands;
    sinTheta = Math.sin(theta);
    cosTheta = Math.cos(theta);
    for (lon = 0;lon <= longitudeBands;lon++) {
      phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;
      sinPhi = Math.sin(phi);
      cosPhi = Math.cos(phi);
      x = cosPhi * sinTheta;
      y = cosTheta;
      z = sinPhi * sinTheta;
      u = 1 - lon / longitudeBands;
      v = 1 - lat / latitudeBands;
      positions.push(x * radius, y * radius, z * radius);
      normals.push(x, y, z);
      uvs.push(u, v);
    }
  }
  for (lat = 0;lat < latitudeBands;++lat) {
    for (lon = 0;lon < longitudeBands;++lon) {
      first = lat * (longitudeBands + 1) + lon;
      second = first + longitudeBands + 1;
      indices.push(first + 1, second, first);
      indices.push(first + 1, second + 1, second);
    }
  }
  var options = {normals:normals, uvs:uvs, uvs1:uvs, indices:indices};
  if (pc.precalculatedTangents) {
    options.tangents = pc.calculateTangents(positions, normals, uvs, indices);
  }
  return pc.createMesh(device, positions, options);
};
pc.createPlane = function(device, opts) {
  var he = opts && opts.halfExtents !== undefined ? opts.halfExtents : new pc.Vec2(.5, .5);
  var ws = opts && opts.widthSegments !== undefined ? opts.widthSegments : 5;
  var ls = opts && opts.lengthSegments !== undefined ? opts.lengthSegments : 5;
  var i, j;
  var x, y, z, u, v;
  var positions = [];
  var normals = [];
  var uvs = [];
  var indices = [];
  for (i = 0;i <= ws;i++) {
    for (j = 0;j <= ls;j++) {
      x = -he.x + 2 * he.x * i / ws;
      y = 0;
      z = -(-he.y + 2 * he.y * j / ls);
      u = i / ws;
      v = j / ls;
      positions.push(x, y, z);
      normals.push(0, 1, 0);
      uvs.push(u, v);
      if (i < ws && j < ls) {
        indices.push(j + i * (ws + 1), j + (i + 1) * (ws + 1), j + i * (ws + 1) + 1);
        indices.push(j + (i + 1) * (ws + 1), j + (i + 1) * (ws + 1) + 1, j + i * (ws + 1) + 1);
      }
    }
  }
  var options = {normals:normals, uvs:uvs, uvs1:uvs, indices:indices};
  if (pc.precalculatedTangents) {
    options.tangents = pc.calculateTangents(positions, normals, uvs, indices);
  }
  return pc.createMesh(device, positions, options);
};
pc.createBox = function(device, opts) {
  var he = opts && opts.halfExtents !== undefined ? opts.halfExtents : new pc.Vec3(.5, .5, .5);
  var ws = opts && opts.widthSegments !== undefined ? opts.widthSegments : 1;
  var ls = opts && opts.lengthSegments !== undefined ? opts.lengthSegments : 1;
  var hs = opts && opts.heightSegments !== undefined ? opts.heightSegments : 1;
  var corners = [new pc.Vec3(-he.x, -he.y, he.z), new pc.Vec3(he.x, -he.y, he.z), new pc.Vec3(he.x, he.y, he.z), new pc.Vec3(-he.x, he.y, he.z), new pc.Vec3(he.x, -he.y, -he.z), new pc.Vec3(-he.x, -he.y, -he.z), new pc.Vec3(-he.x, he.y, -he.z), new pc.Vec3(he.x, he.y, -he.z)];
  var faceAxes = [[0, 1, 3], [4, 5, 7], [3, 2, 6], [1, 0, 4], [1, 4, 2], [5, 0, 6]];
  var faceNormals = [[0, 0, 1], [0, 0, -1], [0, 1, 0], [0, -1, 0], [1, 0, 0], [-1, 0, 0]];
  var sides = {FRONT:0, BACK:1, TOP:2, BOTTOM:3, RIGHT:4, LEFT:5};
  var side, i, j;
  var positions = [];
  var normals = [];
  var uvs = [];
  var uvs1 = [];
  var indices = [];
  var generateFace = function(side, uSegments, vSegments) {
    var x, y, z, u, v;
    var i, j;
    var offset = positions.length / 3;
    for (i = 0;i <= uSegments;i++) {
      for (j = 0;j <= vSegments;j++) {
        var temp1 = new pc.Vec3;
        var temp2 = new pc.Vec3;
        var temp3 = new pc.Vec3;
        var r = new pc.Vec3;
        temp1.lerp(corners[faceAxes[side][0]], corners[faceAxes[side][1]], i / uSegments);
        temp2.lerp(corners[faceAxes[side][0]], corners[faceAxes[side][2]], j / vSegments);
        temp3.sub2(temp2, corners[faceAxes[side][0]]);
        r.add2(temp1, temp3);
        u = i / uSegments;
        v = j / vSegments;
        positions.push(r.x, r.y, r.z);
        normals.push(faceNormals[side][0], faceNormals[side][1], faceNormals[side][2]);
        uvs.push(u, v);
        u /= 3;
        v /= 3;
        u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
        v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
        u += side % 3 / 3;
        v += Math.floor(side / 3) / 3;
        uvs1.push(u, v);
        if (i < uSegments && j < vSegments) {
          indices.push(offset + j + i * (uSegments + 1), offset + j + (i + 1) * (uSegments + 1), offset + j + i * (uSegments + 1) + 1);
          indices.push(offset + j + (i + 1) * (uSegments + 1), offset + j + (i + 1) * (uSegments + 1) + 1, offset + j + i * (uSegments + 1) + 1);
        }
      }
    }
  };
  generateFace(sides.FRONT, ws, hs);
  generateFace(sides.BACK, ws, hs);
  generateFace(sides.TOP, ws, ls);
  generateFace(sides.BOTTOM, ws, ls);
  generateFace(sides.RIGHT, ls, hs);
  generateFace(sides.LEFT, ls, hs);
  var options = {normals:normals, uvs:uvs, uvs1:uvs1, indices:indices};
  if (pc.precalculatedTangents) {
    options.tangents = pc.calculateTangents(positions, normals, uvs, indices);
  }
  return pc.createMesh(device, positions, options);
};
pc.Scene.defaultMaterial = new pc.StandardMaterial;
pc.Scene.defaultMaterial.shadingModel = pc.SPECULAR_BLINN;
pc.extend(pc, function() {
  var Key = function Key(time, position, rotation, scale) {
    this.time = time;
    this.position = position;
    this.rotation = rotation;
    this.scale = scale;
  };
  var Node = function Node() {
    this._name = "";
    this._keys = [];
  };
  var Animation = function Animation() {
    this.name = "";
    this.duration = 0;
    this._nodes = [];
    this._nodeDict = {};
  };
  Animation.prototype.getDuration = function() {
    return this.duration;
  };
  Animation.prototype.getName = function() {
    return this.name;
  };
  Animation.prototype.getNode = function(name) {
    return this._nodeDict[name];
  };
  Object.defineProperty(Animation.prototype, "nodes", {get:function() {
    return this._nodes;
  }});
  Animation.prototype.getNodes = function() {
    return this._nodes;
  };
  Animation.prototype.setDuration = function(value) {
    this.duration = value;
  };
  Animation.prototype.setName = function(value) {
    this.name = value;
  };
  Animation.prototype.addNode = function(node) {
    this._nodes.push(node);
    this._nodeDict[node._name] = node;
  };
  return {Animation:Animation, Key:Key, Node:Node};
}());
pc.extend(pc, function() {
  function InterpolatedKey() {
    this._written = false;
    this._name = "";
    this._keyFrames = [];
    this._quat = new pc.Quat;
    this._pos = new pc.Vec3;
    this._scale = new pc.Vec3;
    this._targetNode = null;
  }
  InterpolatedKey.prototype = {getTarget:function() {
    return this._targetNode;
  }, setTarget:function(node) {
    this._targetNode = node;
  }};
  var Skeleton = function Skeleton(graph) {
    this._animation = null;
    this._time = 0;
    this.looping = true;
    this._interpolatedKeys = [];
    this._interpolatedKeyDict = {};
    this._currKeyIndices = {};
    this.graph = null;
    var self = this;
    function addInterpolatedKeys(node) {
      var interpKey = new InterpolatedKey;
      interpKey._name = node.name;
      self._interpolatedKeys.push(interpKey);
      self._interpolatedKeyDict[node.name] = interpKey;
      self._currKeyIndices[node.name] = 0;
      for (var i = 0;i < node._children.length;i++) {
        addInterpolatedKeys(node._children[i]);
      }
    }
    addInterpolatedKeys(graph);
  };
  Skeleton.prototype.addTime = function(delta) {
    if (this._animation !== null) {
      var i;
      var node, nodeName;
      var keys, interpKey;
      var k1, k2, alpha;
      var nodes = this._animation._nodes;
      var duration = this._animation.duration;
      if (this._time === duration && !this.looping) {
        return;
      }
      this._time += delta;
      if (this._time > duration) {
        this._time = this.looping ? 0 : duration;
        for (i = 0;i < nodes.length;i++) {
          node = nodes[i];
          nodeName = node._name;
          this._currKeyIndices[nodeName] = 0;
        }
      } else {
        if (this._time < 0) {
          this._time = this.looping ? duration : 0;
          for (i = 0;i < nodes.length;i++) {
            node = nodes[i];
            nodeName = node._name;
            this._currKeyIndices[nodeName] = node._keys.length - 2;
          }
        }
      }
      var offset = delta >= 0 ? 1 : -1;
      for (i = 0;i < nodes.length;i++) {
        node = nodes[i];
        nodeName = node._name;
        keys = node._keys;
        interpKey = this._interpolatedKeyDict[nodeName];
        if (keys.length === 1) {
          interpKey._pos.copy(keys[0].position);
          interpKey._quat.copy(keys[0].rotation);
          interpKey._scale(keys[0].scale);
        } else {
          for (var currKeyIndex = this._currKeyIndices[nodeName];currKeyIndex < keys.length - 1 && currKeyIndex >= 0;currKeyIndex += offset) {
            k1 = keys[currKeyIndex];
            k2 = keys[currKeyIndex + 1];
            if (k1.time <= this._time && k2.time >= this._time) {
              alpha = (this._time - k1.time) / (k2.time - k1.time);
              interpKey._pos.lerp(k1.position, k2.position, alpha);
              interpKey._quat.slerp(k1.rotation, k2.rotation, alpha);
              interpKey._scale.lerp(k1.scale, k2.scale, alpha);
              interpKey._written = true;
              this._currKeyIndices[nodeName] = currKeyIndex;
              break;
            }
          }
        }
      }
    }
  };
  Skeleton.prototype.blend = function(skel1, skel2, alpha) {
    var numNodes = this._interpolatedKeys.length;
    for (var i = 0;i < numNodes;i++) {
      var key1 = skel1._interpolatedKeys[i];
      var key2 = skel2._interpolatedKeys[i];
      var dstKey = this._interpolatedKeys[i];
      if (key1._written && key2._written) {
        dstKey._quat.slerp(key1._quat, skel2._interpolatedKeys[i]._quat, alpha);
        dstKey._pos.lerp(key1._pos, skel2._interpolatedKeys[i]._pos, alpha);
        dstKey._scale.lerp(key1._scale, key2._scale, alpha);
        dstKey._written = true;
      } else {
        if (key1._written) {
          dstKey._quat.copy(key1._quat);
          dstKey._pos.copy(key1._pos);
          dstKey._scale.copy(key1._scale);
          dstKey._written = true;
        } else {
          if (key2._written) {
            dstKey._quat.copy(key2._quat);
            dstKey._pos.copy(key2._pos);
            dstKey._scale.copy(key2._scale);
            dstKey._written = true;
          }
        }
      }
    }
  };
  Object.defineProperty(Skeleton.prototype, "animation", {get:function() {
    return this._animation;
  }, set:function(value) {
    this._animation = value;
    this.currentTime = 0;
  }});
  Skeleton.prototype.getAnimation = function() {
    return this._animation;
  };
  Object.defineProperty(Skeleton.prototype, "currentTime", {get:function() {
    return this._time;
  }, set:function(value) {
    this._time = value;
    var numNodes = this._interpolatedKeys.length;
    for (var i = 0;i < numNodes;i++) {
      var node = this._interpolatedKeys[i];
      var nodeName = node._name;
      this._currKeyIndices[nodeName] = 0;
    }
    this.addTime(0);
    this.updateGraph();
  }});
  Skeleton.prototype.getCurrentTime = function() {
    return this._time;
  };
  Skeleton.prototype.setCurrentTime = function(time) {
    this.currentTime = time;
  };
  Object.defineProperty(Skeleton.prototype, "numNodes", {get:function() {
    return this._interpolatedKeys.length;
  }});
  Skeleton.prototype.getNumNodes = function() {
    return this._interpolatedKeys.length;
  };
  Skeleton.prototype.setAnimation = function(animation) {
    this.animation = animation;
  };
  Skeleton.prototype.setGraph = function(graph) {
    var i;
    this.graph = graph;
    if (graph) {
      for (i = 0;i < this._interpolatedKeys.length;i++) {
        var interpKey = this._interpolatedKeys[i];
        var graphNode = graph.findByName(interpKey._name);
        this._interpolatedKeys[i].setTarget(graphNode);
      }
    } else {
      for (i = 0;i < this._interpolatedKeys.length;i++) {
        this._interpolatedKeys[i].setTarget(null);
      }
    }
  };
  Skeleton.prototype.updateGraph = function() {
    if (this.graph) {
      for (var i = 0;i < this._interpolatedKeys.length;i++) {
        var interpKey = this._interpolatedKeys[i];
        if (interpKey._written) {
          var transform = interpKey.getTarget();
          transform.localPosition.copy(interpKey._pos);
          transform.localRotation.copy(interpKey._quat);
          transform.localScale.copy(interpKey._scale);
          transform.dirtyLocal = true;
          interpKey._written = false;
        }
      }
    }
  };
  Skeleton.prototype.setLooping = function(looping) {
    this.looping = looping;
  };
  Skeleton.prototype.getLooping = function() {
    return this.looping;
  };
  return {Skeleton:Skeleton};
}());
pc.extend(pc, function() {
  function hasAudio() {
    return typeof Audio !== "undefined";
  }
  function hasAudioContext() {
    return !!(typeof AudioContext !== "undefined" || typeof webkitAudioContext !== "undefined");
  }
  var SoundManager = function(options) {
    if (hasAudioContext() || options.forceWebAudioApi) {
      if (typeof AudioContext !== "undefined") {
        this.context = new AudioContext;
      } else {
        if (typeof webkitAudioContext !== "undefined") {
          this.context = new webkitAudioContext;
        }
      }
      if (this.context) {
        var context = this.context;
        var iOS = /iPad|iPhone|iPod/.test(navigator.platform);
        if (iOS) {
          var unlock = function() {
            var buffer = context.createBuffer(1, 1, 44100);
            var source = context.createBufferSource();
            source.buffer = buffer;
            source.connect(context.destination);
            source.start(0);
            source.disconnect();
            window.removeEventListener("touchend", unlock);
          };
          window.addEventListener("touchend", unlock);
        }
      }
    } else {
      console.warn("No support for 3D audio found");
    }
    if (!hasAudio()) {
      console.warn("No support for 2D audio found");
    }
    this.listener = new pc.Listener(this);
    this._volume = 1;
    this.suspended = false;
    pc.events.attach(this);
  };
  SoundManager.hasAudio = hasAudio;
  SoundManager.hasAudioContext = hasAudioContext;
  SoundManager.prototype = {suspend:function() {
    this.suspended = true;
    this.fire("suspend");
  }, resume:function() {
    this.suspended = false;
    this.fire("resume");
  }, destroy:function() {
    this.fire("destroy");
    if (this.context && this.context.close) {
      this.context.close();
      this.context = null;
    }
  }, getListener:function() {
    console.warn('DEPRECATED: getListener is deprecated. Get the "listener" field instead.');
    return this.listener;
  }, getVolume:function() {
    console.warn('DEPRECATED: getVolume is deprecated. Get the "volume" property instead.');
    return this.volume;
  }, setVolume:function(volume) {
    console.warn('DEPRECATED: setVolume is deprecated. Set the "volume" property instead.');
    this.volume = volume;
  }, playSound:function(sound, options) {
    options = options || {};
    var channel = null;
    if (pc.Channel) {
      channel = new pc.Channel(this, sound, options);
      channel.play();
    }
    return channel;
  }, playSound3d:function(sound, position, options) {
    options = options || {};
    var channel = null;
    if (pc.Channel3d) {
      channel = new pc.Channel3d(this, sound, options);
      channel.setPosition(position);
      if (options.volume) {
        channel.setVolume(options.volume);
      }
      if (options.loop) {
        channel.setLoop(options.loop);
      }
      if (options.maxDistance) {
        channel.setMaxDistance(options.maxDistance);
      }
      if (options.minDistance) {
        channel.setMinDistance(options.minDistance);
      }
      if (options.rollOffFactor) {
        channel.setRollOffFactor(options.rollOffFactor);
      }
      if (options.distanceModel) {
        channel.setDistanceModel(options.distanceModel);
      }
      channel.play();
    }
    return channel;
  }};
  Object.defineProperty(SoundManager.prototype, "volume", {get:function() {
    return this._volume;
  }, set:function(volume) {
    volume = pc.math.clamp(volume, 0, 1);
    this._volume = volume;
    this.fire("volumechange", volume);
  }});
  pc.AudioManager = SoundManager;
  return {SoundManager:SoundManager};
}());
pc.extend(pc, function() {
  var Sound = function(resource) {
    if (resource instanceof Audio) {
      this.audio = resource;
    } else {
      this.buffer = resource;
    }
  };
  Object.defineProperty(Sound.prototype, "duration", {get:function() {
    var duration = 0;
    if (this.buffer) {
      duration = this.buffer.duration;
    } else {
      if (this.audio) {
        duration = this.audio.duration;
      }
    }
    return duration || 0;
  }});
  return {Sound:Sound};
}());
pc.extend(pc, function() {
  var Listener = function(manager) {
    this.position = new pc.Vec3;
    this.velocity = new pc.Vec3;
    this.orientation = new pc.Mat4;
    if (pc.AudioManager.hasAudioContext()) {
      this.listener = manager.context.listener;
    }
  };
  Listener.prototype = {getPosition:function() {
    return this.position;
  }, setPosition:function(position) {
    this.position.copy(position);
    if (this.listener) {
      this.listener.setPosition(position.x, position.y, position.z);
    }
  }, getVelocity:function() {
    return this.velocity;
  }, setVelocity:function(velocity) {
    this.velocity.copy(velocity);
    if (this.listener) {
      this.listener.setPosition(velocity.x, velocity.y, velocity.z);
    }
  }, setOrientation:function(orientation) {
    this.orientation.copy(orientation);
    if (this.listener) {
      this.listener.setOrientation(-orientation.data[8], -orientation.data[9], -orientation.data[10], orientation.data[4], orientation.data[5], orientation.data[6]);
    }
  }, getOrientation:function() {
    return this.orientation;
  }};
  return {Listener:Listener};
}());
pc.extend(pc, function() {
  var SoundInstance;
  var STATE_PLAYING = 0;
  var STATE_PAUSED = 1;
  var STATE_STOPPED = 2;
  var capTime = function(time, duration) {
    return time % duration || 0;
  };
  if (pc.SoundManager.hasAudioContext()) {
    SoundInstance = function(manager, sound, options) {
      pc.events.attach(this);
      options = options || {};
      this._volume = options.volume !== undefined ? pc.math.clamp(Number(options.volume) || 0, 0, 1) : 1;
      this._pitch = options.pitch !== undefined ? Math.max(.01, Number(options.pitch) || 0) : 1;
      this._loop = !!(options.loop !== undefined ? options.loop : false);
      this._sound = sound;
      this._state = STATE_STOPPED;
      this._suspended = false;
      this._suspendEndEvent = false;
      this._suspendInstanceEvents = false;
      this._startTime = Math.max(0, Number(options.startTime) || 0);
      this._duration = Math.max(0, Number(options.duration) || 0);
      this._startedAt = 0;
      this._startOffset = null;
      this._currentTime = 0;
      this._calculatedCurrentTimeAt = 0;
      this._playWhenLoaded = true;
      this._manager = manager;
      this._inputNode = null;
      this._connectorNode = null;
      this._firstNode = null;
      this._lastNode = null;
      this._initializeNodes();
      this._onPlayCallback = options.onPlay;
      this._onPauseCallback = options.onPause;
      this._onResumeCallback = options.onResume;
      this._onStopCallback = options.onStop;
      this._onEndCallback = options.onEnd;
      this._endedHandler = this._onEnded.bind(this);
      this.source = null;
    };
    SoundInstance.prototype = {_initializeNodes:function() {
      this.gain = this._manager.context.createGain();
      this._inputNode = this.gain;
      this._connectorNode = this.gain;
      this._connectorNode.connect(this._manager.context.destination);
    }, play:function() {
      if (this._state !== STATE_STOPPED) {
        this.stop();
      }
      if (!this.source) {
        this._createSource();
      }
      var offset = capTime(this._startOffset, this.duration);
      offset = capTime(this._startTime + offset, this._sound.duration);
      this._startOffset = null;
      if (this._duration) {
        this.source.start(0, offset, this._duration);
      } else {
        this.source.start(0, offset);
      }
      this._startedAt = this._manager.context.currentTime - offset;
      this._currentTime = 0;
      this._calculatedCurrentTimeAt = this._manager.context.currentTime;
      this._state = STATE_PLAYING;
      this._playWhenLoaded = false;
      this.volume = this._volume;
      this.loop = this._loop;
      this.pitch = this._pitch;
      this._manager.on("volumechange", this._onManagerVolumeChange, this);
      this._manager.on("suspend", this._onManagerSuspend, this);
      this._manager.on("resume", this._onManagerResume, this);
      this._manager.on("destroy", this._onManagerDestroy, this);
      if (this._manager.suspended) {
        this._onManagerSuspend();
      }
      if (!this._suspendInstanceEvents) {
        this._onPlay();
      }
      return true;
    }, pause:function() {
      if (this._state !== STATE_PLAYING || !this.source) {
        return false;
      }
      this._state = STATE_PAUSED;
      this._currentTime = capTime(this._currentTime + (this._manager.context.currentTime - this._calculatedCurrentTimeAt) * this.pitch, this.duration);
      this._calculatedCurrentTimeAt = this._manager.context.currentTime;
      this._suspendEndEvent = true;
      this.source.stop(0);
      this.source = null;
      this._playWhenLoaded = false;
      this._startOffset = null;
      if (!this._suspendInstanceEvents) {
        this._onPause();
      }
      return true;
    }, resume:function() {
      if (this._state !== STATE_PAUSED) {
        return false;
      }
      if (!this.source) {
        this._createSource();
      }
      var offset = capTime(this._startTime + this._currentTime, this._sound.duration);
      if (this._startOffset !== null) {
        offset = capTime(this._startOffset, this.duration);
        offset = capTime(this._startTime + offset, this._sound.duration);
        this._startOffset = null;
      }
      if (this._duration) {
        this.source.start(0, offset, this._duration);
      } else {
        this.source.start(0, offset);
      }
      this._state = STATE_PLAYING;
      this.volume = this._volume;
      this.loop = this._loop;
      this.pitch = this._pitch;
      this._playWhenLoaded = false;
      if (!this._suspendInstanceEvents) {
        this._onResume();
      }
      return true;
    }, stop:function() {
      if (this._state === STATE_STOPPED || !this.source) {
        return false;
      }
      this._manager.off("volumechange", this._onManagerVolumeChange, this);
      this._manager.off("suspend", this._onManagerSuspend, this);
      this._manager.off("resume", this._onManagerResume, this);
      this._manager.off("destroy", this._onManagerDestroy, this);
      this._startedAt = 0;
      this._startOffset = null;
      this._playWhenLoaded = false;
      this._suspendEndEvent = true;
      if (this._state === STATE_PLAYING) {
        this.source.stop(0);
      }
      this.source = null;
      this._state = STATE_STOPPED;
      if (!this._suspendInstanceEvents) {
        this._onStop();
      }
      return true;
    }, setExternalNodes:function(firstNode, lastNode) {
      if (!firstNode) {
        console.error("The firstNode must be a valid Audio Node");
        return;
      }
      if (!lastNode) {
        lastNode = firstNode;
      }
      var speakers = this._manager.context.destination;
      if (this._firstNode !== firstNode) {
        if (this._firstNode) {
          this._connectorNode.disconnect(this._firstNode);
        } else {
          this._connectorNode.disconnect(speakers);
        }
        this._firstNode = firstNode;
        this._connectorNode.connect(firstNode);
      }
      if (this._lastNode !== lastNode) {
        if (this._lastNode) {
          this._lastNode.disconnect(speakers);
        }
        this._lastNode = lastNode;
        this._lastNode.connect(speakers);
      }
    }, clearExternalNodes:function() {
      var speakers = this._manager.context.destination;
      if (this._firstNode) {
        this._connectorNode.disconnect(this._firstNode);
        this._firstNode = null;
      }
      if (this._lastNode) {
        this._lastNode.disconnect(speakers);
        this._lastNode = null;
      }
      this._connectorNode.connect(speakers);
    }, getExternalNodes:function() {
      return [this._firstNode, this._lastNode];
    }, _createSource:function() {
      if (!this._sound) {
        return null;
      }
      var context = this._manager.context;
      if (this._sound.buffer) {
        this.source = context.createBufferSource();
        this.source.buffer = this._sound.buffer;
        this.source.connect(this._inputNode);
        this.source.onended = this._endedHandler;
        this.source.loopStart = capTime(this._startTime, this.source.buffer.duration);
        if (this._duration) {
          this.source.loopEnd = Math.max(this.source.loopStart, capTime(this._startTime + this._duration, this.source.buffer.duration));
        }
      }
      return this.source;
    }, _onManagerDestroy:function() {
      if (this.source && this.isPlaying) {
        this.source.stop(0);
        this.source = null;
      }
    }};
    Object.defineProperty(SoundInstance.prototype, "volume", {get:function() {
      return this._volume;
    }, set:function(volume) {
      volume = pc.math.clamp(volume, 0, 1);
      this._volume = volume;
      if (this.gain) {
        this.gain.gain.value = volume * this._manager.volume;
      }
    }});
    Object.defineProperty(SoundInstance.prototype, "pitch", {get:function() {
      return this._pitch;
    }, set:function(pitch) {
      var old = this._pitch;
      if (this._calculatedCurrentTimeAt) {
        this._currentTime = capTime(this._currentTime + (this._manager.context.currentTime - this._calculatedCurrentTimeAt) * old, this.duration);
        this._calculatedCurrentTimeAt = this._manager.context.currentTime;
      }
      this._pitch = Math.max(Number(pitch) || 0, .01);
      if (this.source) {
        this.source.playbackRate.value = this._pitch;
      }
    }});
    Object.defineProperty(SoundInstance.prototype, "loop", {get:function() {
      return this._loop;
    }, set:function(loop) {
      this._loop = !!loop;
      if (this.source) {
        this.source.loop = this._loop;
      }
    }});
    Object.defineProperty(SoundInstance.prototype, "sound", {get:function() {
      return this._sound;
    }, set:function(value) {
      this._sound = value;
      if (!this.isStopped) {
        this.stop();
      } else {
        this._createSource();
      }
    }});
    Object.defineProperty(SoundInstance.prototype, "currentTime", {get:function() {
      if (this._startOffset !== null) {
        return this._startOffset;
      }
      if (this.isStopped || !this.source) {
        return 0;
      }
      if (this.isPaused) {
        return this._currentTime;
      }
      this._currentTime = capTime(this._currentTime + (this._manager.context.currentTime - this._calculatedCurrentTimeAt) * this.pitch, this.duration);
      this._calculatedCurrentTimeAt = this._manager.context.currentTime;
      return this._currentTime;
    }, set:function(value) {
      if (value < 0) {
        return;
      }
      if (this.isPlaying) {
        this.stop();
        var suspend = this._suspendInstanceEvents;
        this._suspendInstanceEvents = true;
        this._startOffset = value;
        this.play();
        this._suspendInstanceEvents = suspend;
      } else {
        this._startOffset = value;
        this._currentTime = value;
        this._calculatedCurrentTimeAt = this._manager.context.currentTime;
      }
    }});
  } else {
    if (pc.SoundManager.hasAudio()) {
      SoundInstance = function(manager, resource, options) {
        pc.events.attach(this);
        options = options || {};
        this._volume = options.volume !== undefined ? pc.math.clamp(Number(options.volume) || 0, 0, 1) : 1;
        this._pitch = options.pitch !== undefined ? Math.max(.01, Number(options.pitch) || 0) : 1;
        this._loop = !!(options.loop !== undefined ? options.loop : false);
        this._sound = resource;
        this._state = STATE_STOPPED;
        this._suspended = false;
        this._suspendEndEvent = false;
        this._suspendInstanceEvents = false;
        this._playWhenLoaded = true;
        this._startTime = Math.max(0, Number(options.startTime) || 0);
        this._duration = Math.max(0, Number(options.duration) || 0);
        this._startOffset = null;
        this._isReady = false;
        this._manager = manager;
        this._loadedMetadataHandler = this._onLoadedMetadata.bind(this);
        this._timeUpdateHandler = this._onTimeUpdate.bind(this);
        this._endedHandler = this._onEnded.bind(this);
        this._onPlayCallback = options.onPlay;
        this._onPauseCallback = options.onPause;
        this._onResumeCallback = options.onResume;
        this._onStopCallback = options.onStop;
        this._onEndCallback = options.onEnd;
        this.source = null;
        this._createSource();
      };
      SoundInstance.prototype = {play:function() {
        if (this._state !== STATE_STOPPED) {
          this.stop();
        }
        if (!this.source) {
          return false;
        }
        this.volume = this._volume;
        this.pitch = this._pitch;
        this.loop = this._loop;
        this.source.play();
        this._state = STATE_PLAYING;
        this._playWhenLoaded = false;
        this._manager.on("volumechange", this._onManagerVolumeChange, this);
        this._manager.on("suspend", this._onManagerSuspend, this);
        this._manager.on("resume", this._onManagerResume, this);
        this._manager.on("destroy", this._onManagerDestroy, this);
        if (this._manager.suspended) {
          this._onManagerSuspend();
        }
        if (!this._suspendInstanceEvents) {
          this._onPlay();
        }
        return true;
      }, pause:function() {
        if (!this.source || this._state !== STATE_PLAYING) {
          return false;
        }
        this._suspendEndEvent = true;
        this.source.pause();
        this._playWhenLoaded = false;
        this._state = STATE_PAUSED;
        this._startOffset = null;
        if (!this._suspendInstanceEvents) {
          this._onPause();
        }
        return true;
      }, resume:function() {
        if (!this.source || this._state !== STATE_PAUSED) {
          return false;
        }
        this._state = STATE_PLAYING;
        this._playWhenLoaded = false;
        if (this.source.paused) {
          this.source.play();
          if (!this._suspendInstanceEvents) {
            this._onResume();
          }
        }
        return true;
      }, stop:function() {
        if (!this.source || this._state === STATE_STOPPED) {
          return false;
        }
        this._manager.off("volumechange", this._onManagerVolumeChange, this);
        this._manager.off("suspend", this._onManagerSuspend, this);
        this._manager.off("resume", this._onManagerResume, this);
        this._manager.off("destroy", this._onManagerDestroy, this);
        this._suspendEndEvent = true;
        this.source.pause();
        this._playWhenLoaded = false;
        this._state = STATE_STOPPED;
        this._startOffset = null;
        if (!this._suspendInstanceEvents) {
          this._onStop();
        }
        return true;
      }, setExternalNodes:function() {
      }, clearExternalNodes:function() {
      }, getExternalNodes:function() {
        return [null, null];
      }, _onLoadedMetadata:function() {
        this.source.removeEventListener("loadedmetadata", this._loadedMetadataHandler);
        this._isReady = true;
        var offset = capTime(this._startOffset, this.duration);
        offset = capTime(this._startTime + offset, this._sound.duration);
        this._startOffset = null;
        this.source.currentTime = offset;
      }, _createSource:function() {
        if (this._sound && this._sound.audio) {
          this._isReady = false;
          this.source = this._sound.audio.cloneNode(true);
          this.source.addEventListener("loadedmetadata", this._loadedMetadataHandler);
          this.source.addEventListener("timeupdate", this._timeUpdateHandler);
          this.source.onended = this._endedHandler;
        }
        return this.source;
      }, _onTimeUpdate:function() {
        if (!this._duration) {
          return;
        }
        if (this.source.currentTime > capTime(this._startTime + this._duration, this.source.duration)) {
          if (this.loop) {
            this.source.currentTime = capTime(this._startTime, this.source.duration);
          } else {
            this.source.removeEventListener("timeupdate", this._timeUpdateHandler);
            this.source.pause();
            this._onEnded();
          }
        }
      }, _onManagerDestroy:function() {
        if (this.source) {
          this.source.pause();
        }
      }};
      Object.defineProperty(SoundInstance.prototype, "volume", {get:function() {
        return this._volume;
      }, set:function(volume) {
        volume = pc.math.clamp(volume, 0, 1);
        this._volume = volume;
        if (this.source) {
          this.source.volume = volume * this._manager.volume;
        }
      }});
      Object.defineProperty(SoundInstance.prototype, "pitch", {get:function() {
        return this._pitch;
      }, set:function(pitch) {
        this._pitch = Math.max(Number(pitch) || 0, .01);
        if (this.source) {
          this.source.playbackRate = this._pitch;
        }
      }});
      Object.defineProperty(SoundInstance.prototype, "loop", {get:function() {
        return this._loop;
      }, set:function(loop) {
        this._loop = !!loop;
        if (this.source) {
          this.source.loop = this._loop;
        }
      }});
      Object.defineProperty(SoundInstance.prototype, "sound", {get:function() {
        return this._sound;
      }, set:function(value) {
        this.stop();
        this._sound = value;
      }});
      Object.defineProperty(SoundInstance.prototype, "currentTime", {get:function() {
        if (this._startOffset !== null) {
          return this._startOffset;
        }
        if (this.isStopped || !this.source) {
          return 0;
        }
        return this.source.currentTime - this._startTime;
      }, set:function(value) {
        if (value < 0) {
          return;
        }
        this._startOffset = value;
        if (this.source && this._isReady) {
          this.source.currentTime = capTime(this._startTime + capTime(value, this.duration), this._sound.duration);
          this._startOffset = null;
        }
      }});
    } else {
      SoundInstance = function() {
      };
    }
  }
  pc.extend(SoundInstance.prototype, {_onPlay:function() {
    this.fire("play");
    if (this._onPlayCallback) {
      this._onPlayCallback(this);
    }
  }, _onPause:function() {
    this.fire("pause");
    if (this._onPauseCallback) {
      this._onPauseCallback(this);
    }
  }, _onResume:function() {
    this.fire("resume");
    if (this._onResumeCallback) {
      this._onResumeCallback(this);
    }
  }, _onStop:function() {
    this.fire("stop");
    if (this._onStopCallback) {
      this._onStopCallback(this);
    }
  }, _onEnded:function() {
    if (this._suspendEndEvent) {
      this._suspendEndEvent = false;
      return;
    }
    this.fire("end");
    if (this._onEndCallback) {
      this._onEndCallback(this);
    }
    this.stop();
  }, _onManagerVolumeChange:function() {
    this.volume = this._volume;
  }, _onManagerSuspend:function() {
    if (this.isPlaying && !this._suspended) {
      this._suspended = true;
      this.pause();
    }
  }, _onManagerResume:function() {
    if (this._suspended) {
      this._suspended = false;
      this.resume();
    }
  }});
  Object.defineProperty(SoundInstance.prototype, "startTime", {get:function() {
    return this._startTime;
  }, set:function(value) {
    this._startTime = Math.max(0, Number(value) || 0);
    var isPlaying = this.isPlaying;
    this.stop();
    if (isPlaying) {
      this.play();
    }
  }});
  Object.defineProperty(SoundInstance.prototype, "duration", {get:function() {
    if (!this._sound) {
      return 0;
    }
    if (this._duration) {
      return capTime(this._duration, this._sound.duration);
    } else {
      return this._sound.duration;
    }
  }, set:function(value) {
    this._duration = Math.max(0, Number(value) || 0);
    var isPlaying = this.isPlaying;
    this.stop();
    if (isPlaying) {
      this.play();
    }
  }});
  Object.defineProperty(SoundInstance.prototype, "isPlaying", {get:function() {
    return this._state === STATE_PLAYING;
  }});
  Object.defineProperty(SoundInstance.prototype, "isPaused", {get:function() {
    return this._state === STATE_PAUSED;
  }});
  Object.defineProperty(SoundInstance.prototype, "isStopped", {get:function() {
    return this._state === STATE_STOPPED;
  }});
  Object.defineProperty(SoundInstance.prototype, "isSuspended", {get:function() {
    return this._suspended;
  }});
  return {SoundInstance:SoundInstance};
}());
pc.extend(pc, function() {
  var MAX_DISTANCE = 1E4;
  var SoundInstance3d;
  if (pc.SoundManager.hasAudioContext()) {
    SoundInstance3d = function(manager, sound, options) {
      options = options || {};
      this._position = new pc.Vec3;
      if (options.position) {
        this.position = options.position;
      }
      this._velocity = new pc.Vec3;
      if (options.velocity) {
        this.velocity = options.velocity;
      }
      this.maxDistance = options.maxDistance !== undefined ? Number(options.maxDistance) : MAX_DISTANCE;
      this.refDistance = options.refDistance !== undefined ? Number(options.refDistance) : 1;
      this.rollOffFactor = options.rollOffFactor !== undefined ? Number(options.rollOffFactor) : 1;
      this.distanceModel = options.distanceModel !== undefined ? options.distanceModel : pc.DISTANCE_LINEAR;
    };
    SoundInstance3d = pc.inherits(SoundInstance3d, pc.SoundInstance);
    SoundInstance3d.prototype = pc.extend(SoundInstance3d.prototype, {_initializeNodes:function() {
      this.gain = this._manager.context.createGain();
      this.panner = this._manager.context.createPanner();
      this.panner.connect(this.gain);
      this._inputNode = this.panner;
      this._connectorNode = this.gain;
      this._connectorNode.connect(this._manager.context.destination);
    }});
    Object.defineProperty(SoundInstance3d.prototype, "position", {get:function() {
      return this._position;
    }, set:function(position) {
      this._position.copy(position);
      this.panner.setPosition(position.x, position.y, position.z);
    }});
    Object.defineProperty(SoundInstance3d.prototype, "velocity", {get:function() {
      return this._velocity;
    }, set:function(velocity) {
      this._velocity.copy(velocity);
      this.panner.setVelocity(velocity.x, velocity.y, velocity.z);
    }});
    Object.defineProperty(SoundInstance3d.prototype, "maxDistance", {get:function() {
      return this.panner.maxDistance;
    }, set:function(value) {
      this.panner.maxDistance = value;
    }});
    Object.defineProperty(SoundInstance3d.prototype, "refDistance", {get:function() {
      return this.panner.refDistance;
    }, set:function(value) {
      this.panner.refDistance = value;
    }});
    Object.defineProperty(SoundInstance3d.prototype, "rollOffFactor", {get:function() {
      return this.panner.rollOffFactor;
    }, set:function(value) {
      this.panner.rollOffFactor = value;
    }});
    Object.defineProperty(SoundInstance3d.prototype, "distanceModel", {get:function() {
      return this.panner.distanceModel;
    }, set:function(value) {
      this.panner.distanceModel = value;
    }});
  } else {
    if (pc.SoundManager.hasAudio()) {
      var offset = new pc.Vec3;
      var fallOff = function(posOne, posTwo, refDistance, maxDistance, rollOffFactor, distanceModel) {
        offset = offset.sub2(posOne, posTwo);
        var distance = offset.length();
        if (distance < refDistance) {
          return 1;
        } else {
          if (distance > maxDistance) {
            return 0;
          } else {
            var result = 0;
            if (distanceModel === pc.DISTANCE_LINEAR) {
              result = 1 - rollOffFactor * (distance - refDistance) / (maxDistance - refDistance);
            } else {
              if (distanceModel === pc.DISTANCE_INVERSE) {
                result = refDistance / (refDistance + rollOffFactor * (distance - refDistance));
              } else {
                if (distanceModel === pc.DISTANCE_EXPONENTIAL) {
                  result = Math.pow(distance / refDistance, -rollOffFactor);
                }
              }
            }
            return pc.math.clamp(result, 0, 1);
          }
        }
      };
      SoundInstance3d = function(manager, sound, options) {
        options = options || {};
        this._position = new pc.Vec3;
        if (options.position) {
          this.position = options.position;
        }
        this._velocity = new pc.Vec3;
        if (options.velocity) {
          this.velocity = options.velocity;
        }
        this._maxDistance = options.maxDistance !== undefined ? Number(options.maxDistance) : MAX_DISTANCE;
        this._refDistance = options.refDistance !== undefined ? Number(options.refDistance) : 1;
        this._rollOffFactor = options.rollOffFactor !== undefined ? Number(options.rollOffFactor) : 1;
        this._distanceModel = options.distanceModel !== undefined ? options.distanceModel : pc.DISTANCE_LINEAR;
      };
      SoundInstance3d = pc.inherits(SoundInstance3d, pc.SoundInstance);
      Object.defineProperty(SoundInstance3d.prototype, "position", {get:function() {
        return this._position;
      }, set:function(position) {
        this._position.copy(position);
        if (this.source) {
          var listener = this._manager.listener;
          var lpos = listener.getPosition();
          var factor = fallOff(lpos, this._position, this.refDistance, this.maxDistance, this.rollOffFactor, this.distanceModel);
          var v = this.volume;
          this.source.volume = v * factor * this._manager.volume;
        }
      }});
      Object.defineProperty(SoundInstance3d.prototype, "velocity", {get:function() {
        return this._velocity;
      }, set:function(velocity) {
        this._velocity.copy(velocity);
      }});
      Object.defineProperty(SoundInstance3d.prototype, "maxDistance", {get:function() {
        return this._maxDistance;
      }, set:function(value) {
        this._maxDistance = value;
      }});
      Object.defineProperty(SoundInstance3d.prototype, "refDistance", {get:function() {
        return this._refDistance;
      }, set:function(value) {
        this._refDistance = value;
      }});
      Object.defineProperty(SoundInstance3d.prototype, "rollOffFactor", {get:function() {
        return this._rollOffFactor;
      }, set:function(value) {
        this._rollOffFactor = value;
      }});
      Object.defineProperty(SoundInstance3d.prototype, "distanceModel", {get:function() {
        return this._distanceModel;
      }, set:function(value) {
        this._distanceModel = value;
      }});
    } else {
      SoundInstance3d = function() {
      };
    }
  }
  return {SoundInstance3d:SoundInstance3d};
}());
pc.extend(pc, function() {
  var Channel;
  if (pc.AudioManager.hasAudioContext()) {
    Channel = function(manager, sound, options) {
      options = options || {};
      this.volume = options.volume === undefined ? 1 : options.volume;
      this.loop = options.loop === undefined ? false : options.loop;
      this.pitch = options.pitch === undefined ? 1 : options.pitch;
      this.sound = sound;
      this.paused = false;
      this.suspended = false;
      this.startTime = 0;
      this.startOffset = 0;
      this.manager = manager;
      this.source = null;
      var context = manager.context;
      this.gain = context.createGain();
    };
    Channel.prototype = {play:function() {
      if (this.source) {
        throw new Error("Call stop() before calling play()");
      }
      this._createSource();
      if (!this.source) {
        return;
      }
      this.startTime = this.manager.context.currentTime;
      this.source.start(0, this.startOffset % this.source.buffer.duration);
      this.setVolume(this.volume);
      this.setLoop(this.loop);
      this.setPitch(this.pitch);
      this.manager.on("volumechange", this.onManagerVolumeChange, this);
      this.manager.on("suspend", this.onManagerSuspend, this);
      this.manager.on("resume", this.onManagerResume, this);
      if (this.manager.suspended) {
        this.onManagerSuspend();
      }
    }, pause:function() {
      if (this.source) {
        this.paused = true;
        this.startOffset += this.manager.context.currentTime - this.startTime;
        this.source.stop(0);
        this.source = null;
      }
    }, unpause:function() {
      if (this.source || !this.paused) {
        console.warn("Call pause() before unpausing.");
        return;
      }
      this._createSource();
      if (!this.source) {
        return;
      }
      this.startTime = this.manager.context.currentTime;
      this.source.start(0, this.startOffset % this.source.buffer.duration);
      this.setVolume(this.volume);
      this.setLoop(this.loop);
      this.setPitch(this.pitch);
      this.paused = false;
    }, stop:function() {
      if (this.source) {
        this.source.stop(0);
        this.source = null;
      }
      this.manager.off("volumechange", this.onManagerVolumeChange, this);
      this.manager.off("suspend", this.onManagerSuspend, this);
      this.manager.off("resume", this.onManagerResume, this);
    }, setLoop:function(loop) {
      this.loop = loop;
      if (this.source) {
        this.source.loop = loop;
      }
    }, setVolume:function(volume) {
      volume = pc.math.clamp(volume, 0, 1);
      this.volume = volume;
      if (this.gain) {
        this.gain.gain.value = volume * this.manager.volume;
      }
    }, setPitch:function(pitch) {
      this.pitch = pitch;
      if (this.source) {
        this.source.playbackRate.value = pitch;
      }
    }, isPlaying:function() {
      return !this.paused && this.source.playbackState === this.source.PLAYING_STATE;
    }, getDuration:function() {
      if (this.source) {
        return this.source.buffer.duration;
      } else {
        return 0;
      }
    }, _createSource:function() {
      var context = this.manager.context;
      if (this.sound.buffer) {
        this.source = context.createBufferSource();
        this.source.buffer = this.sound.buffer;
        this.source.connect(this.gain);
        this.gain.connect(context.destination);
        if (!this.loop) {
          this.source.onended = this.pause.bind(this);
        }
      }
    }};
  } else {
    if (pc.AudioManager.hasAudio()) {
      Channel = function(manager, sound, options) {
        this.volume = options.volume || 1;
        this.loop = options.loop || false;
        this.sound = sound;
        this.pitch = options.pitch !== undefined ? options.pitch : 1;
        this.paused = false;
        this.suspended = false;
        this.manager = manager;
        if (sound.audio) {
          this.source = sound.audio.cloneNode(false);
          this.source.pause();
        }
      };
      Channel.prototype = {play:function() {
        if (this.source) {
          this.paused = false;
          this.setVolume(this.volume);
          this.setLoop(this.loop);
          this.setPitch(this.pitch);
          this.source.play();
        }
        this.manager.on("volumechange", this.onManagerVolumeChange, this);
        this.manager.on("suspend", this.onManagerSuspend, this);
        this.manager.on("resume", this.onManagerResume, this);
        if (this.manager.suspended) {
          this.onManagerSuspend();
        }
      }, pause:function() {
        if (this.source) {
          this.paused = true;
          this.source.pause();
        }
      }, unpause:function() {
        if (this.source) {
          this.paused = false;
          this.source.play();
        }
      }, stop:function() {
        if (this.source) {
          this.source.pause();
        }
        this.manager.off("volumechange", this.onManagerVolumeChange, this);
        this.manager.off("suspend", this.onManagerSuspend, this);
        this.manager.off("resume", this.onManagerResume, this);
      }, setVolume:function(volume) {
        volume = pc.math.clamp(volume, 0, 1);
        this.volume = volume;
        if (this.source) {
          this.source.volume = volume * this.manager.volume;
        }
      }, setLoop:function(loop) {
        this.loop = loop;
        if (this.source) {
          this.source.loop = loop;
        }
      }, setPitch:function(pitch) {
        this.pitch = pitch;
        if (this.source) {
          this.source.playbackRate = pitch;
        }
      }, getDuration:function() {
        if (this.source) {
          var d = this.source.duration;
          if (d === d) {
            return d;
          }
        }
        return 0;
      }, isPlaying:function() {
        return !this.source.paused;
      }};
    } else {
      Channel = function() {
      };
    }
  }
  pc.extend(Channel.prototype, {getVolume:function() {
    return this.volume;
  }, getLoop:function() {
    return this.loop;
  }, getPitch:function() {
    return this.pitch;
  }, onManagerVolumeChange:function() {
    this.setVolume(this.getVolume());
  }, onManagerSuspend:function() {
    if (this.isPlaying() && !this.suspended) {
      this.suspended = true;
      this.pause();
    }
  }, onManagerResume:function() {
    if (this.suspended) {
      this.suspended = false;
      this.unpause();
    }
  }});
  return {Channel:Channel};
}());
pc.extend(pc, function() {
  var MAX_DISTANCE = 1E4;
  var Channel3d;
  if (pc.AudioManager.hasAudioContext()) {
    Channel3d = function(manager, sound, options) {
      this.position = new pc.Vec3;
      this.velocity = new pc.Vec3;
      var context = manager.context;
      this.panner = context.createPanner();
    };
    Channel3d = pc.inherits(Channel3d, pc.Channel);
    Channel3d.prototype = pc.extend(Channel3d.prototype, {getPosition:function() {
      return this.position;
    }, setPosition:function(position) {
      this.position.copy(position);
      this.panner.setPosition(position.x, position.y, position.z);
    }, getVelocity:function() {
      return this.velocity;
    }, setVelocity:function(velocity) {
      this.velocity.copy(velocity);
      this.panner.setVelocity(velocity.x, velocity.y, velocity.z);
    }, getMaxDistance:function() {
      return this.panner.maxDistance;
    }, setMaxDistance:function(max) {
      this.panner.maxDistance = max;
    }, getMinDistance:function() {
      return this.panner.refDistance;
    }, setMinDistance:function(min) {
      this.panner.refDistance = min;
    }, getRollOffFactor:function() {
      return this.panner.rolloffFactor;
    }, setRollOffFactor:function(factor) {
      this.panner.rolloffFactor = factor;
    }, getDistanceModel:function() {
      return this.pannel.distanceModel;
    }, setDistanceModel:function(distanceModel) {
      this.panner.distanceModel = distanceModel;
    }, _createSource:function() {
      var context = this.manager.context;
      this.source = context.createBufferSource();
      this.source.buffer = this.sound.buffer;
      this.source.connect(this.panner);
      this.panner.connect(this.gain);
      this.gain.connect(context.destination);
      if (!this.loop) {
        this.source.onended = this.pause.bind(this);
      }
    }});
  } else {
    if (pc.AudioManager.hasAudio()) {
      var offset = new pc.Vec3;
      var fallOff = function(posOne, posTwo, refDistance, maxDistance, rolloffFactor, distanceModel) {
        offset = offset.sub2(posOne, posTwo);
        var distance = offset.length();
        if (distance < refDistance) {
          return 1;
        } else {
          if (distance > maxDistance) {
            return 0;
          } else {
            var result = 0;
            if (distanceModel === pc.DISTANCE_LINEAR) {
              result = 1 - rolloffFactor * (distance - refDistance) / (maxDistance - refDistance);
            } else {
              if (distanceModel === pc.DISTANCE_INVERSE) {
                result = refDistance / (refDistance + rolloffFactor * (distance - refDistance));
              } else {
                if (distanceModel === pc.DISTANCE_EXPONENTIAL) {
                  result = Math.pow(distance / refDistance, -rolloffFactor);
                }
              }
            }
            return pc.math.clamp(result, 0, 1);
          }
        }
      };
      Channel3d = function(manager, sound) {
        this.position = new pc.Vec3;
        this.velocity = new pc.Vec3;
        this.maxDistance = MAX_DISTANCE;
        this.minDistance = 1;
        this.rollOffFactor = 1;
        this.distanceModel = pc.DISTANCE_INVERSE;
      };
      Channel3d = pc.inherits(Channel3d, pc.Channel);
      Channel3d.prototype = pc.extend(Channel3d.prototype, {getPosition:function() {
        return this.position;
      }, setPosition:function(position) {
        this.position.copy(position);
        if (this.source) {
          var listener = this.manager.listener;
          var lpos = listener.getPosition();
          var factor = fallOff(lpos, this.position, this.minDistance, this.maxDistance, this.rollOffFactor, this.distanceModel);
          var v = this.getVolume();
          this.source.volume = v * factor;
        }
      }, getVelocity:function() {
        return this.velocity;
      }, setVelocity:function(velocity) {
        this.velocity.copy(velocity);
      }, getMaxDistance:function() {
        return this.maxDistance;
      }, setMaxDistance:function(max) {
        this.maxDistance = max;
      }, getMinDistance:function() {
        return this.minDistance;
      }, setMinDistance:function(min) {
        this.minDistance = min;
      }, getRollOffFactor:function() {
        return this.rolloffFactor;
      }, setRollOffFactor:function(factor) {
        this.rolloffFactor = factor;
      }, getDistanceModel:function() {
        return this.distanceModel;
      }, setDistanceModel:function(distanceModel) {
        this.distanceModel = distanceModel;
      }});
    } else {
      Channel3d = function() {
      };
    }
  }
  return {Channel3d:Channel3d};
}());
(function() {
  var enums = {ACTION_MOUSE:"mouse", ACTION_KEYBOARD:"keyboard", ACTION_GAMEPAD:"gamepad", AXIS_MOUSE_X:"mousex", AXIS_MOUSE_Y:"mousey", AXIS_PAD_L_X:"padlx", AXIS_PAD_L_Y:"padly", AXIS_PAD_R_X:"padrx", AXIS_PAD_R_Y:"padry", AXIS_KEY:"key", EVENT_KEYDOWN:"keydown", EVENT_KEYUP:"keyup", EVENT_MOUSEDOWN:"mousedown", EVENT_MOUSEMOVE:"mousemove", EVENT_MOUSEUP:"mouseup", EVENT_MOUSEWHEEL:"mousewheel", EVENT_TOUCHSTART:"touchstart", EVENT_TOUCHEND:"touchend", EVENT_TOUCHMOVE:"touchmove", EVENT_TOUCHCANCEL:"touchcancel", 
  KEY_BACKSPACE:8, KEY_TAB:9, KEY_RETURN:13, KEY_ENTER:13, KEY_SHIFT:16, KEY_CONTROL:17, KEY_ALT:18, KEY_PAUSE:19, KEY_CAPS_LOCK:20, KEY_ESCAPE:27, KEY_SPACE:32, KEY_PAGE_UP:33, KEY_PAGE_DOWN:34, KEY_END:35, KEY_HOME:36, KEY_LEFT:37, KEY_UP:38, KEY_RIGHT:39, KEY_DOWN:40, KEY_PRINT_SCREEN:44, KEY_INSERT:45, KEY_DELETE:46, KEY_0:48, KEY_1:49, KEY_2:50, KEY_3:51, KEY_4:52, KEY_5:53, KEY_6:54, KEY_7:55, KEY_8:56, KEY_9:57, KEY_SEMICOLON:59, KEY_EQUAL:61, KEY_A:65, KEY_B:66, KEY_C:67, KEY_D:68, KEY_E:69, 
  KEY_F:70, KEY_G:71, KEY_H:72, KEY_I:73, KEY_J:74, KEY_K:75, KEY_L:76, KEY_M:77, KEY_N:78, KEY_O:79, KEY_P:80, KEY_Q:81, KEY_R:82, KEY_S:83, KEY_T:84, KEY_U:85, KEY_V:86, KEY_W:87, KEY_X:88, KEY_Y:89, KEY_Z:90, KEY_WINDOWS:91, KEY_CONTEXT_MENU:93, KEY_NUMPAD_0:96, KEY_NUMPAD_1:97, KEY_NUMPAD_2:98, KEY_NUMPAD_3:99, KEY_NUMPAD_4:100, KEY_NUMPAD_5:101, KEY_NUMPAD_6:102, KEY_NUMPAD_7:103, KEY_NUMPAD_8:104, KEY_NUMPAD_9:105, KEY_MULTIPLY:106, KEY_ADD:107, KEY_SEPARATOR:108, KEY_SUBTRACT:109, KEY_DECIMAL:110, 
  KEY_DIVIDE:111, KEY_F1:112, KEY_F2:113, KEY_F3:114, KEY_F4:115, KEY_F5:116, KEY_F6:117, KEY_F7:118, KEY_F8:119, KEY_F9:120, KEY_F10:121, KEY_F11:122, KEY_F12:123, KEY_COMMA:188, KEY_PERIOD:190, KEY_SLASH:191, KEY_OPEN_BRACKET:219, KEY_BACK_SLASH:220, KEY_CLOSE_BRACKET:221, KEY_META:224, MOUSEBUTTON_NONE:-1, MOUSEBUTTON_LEFT:0, MOUSEBUTTON_MIDDLE:1, MOUSEBUTTON_RIGHT:2, PAD_1:0, PAD_2:1, PAD_3:2, PAD_4:3, PAD_FACE_1:0, PAD_FACE_2:1, PAD_FACE_3:2, PAD_FACE_4:3, PAD_L_SHOULDER_1:4, PAD_R_SHOULDER_1:5, 
  PAD_L_SHOULDER_2:6, PAD_R_SHOULDER_2:7, PAD_SELECT:8, PAD_START:9, PAD_L_STICK_BUTTON:10, PAD_R_STICK_BUTTON:11, PAD_UP:12, PAD_DOWN:13, PAD_LEFT:14, PAD_RIGHT:15, PAD_VENDOR:16, PAD_L_STICK_X:0, PAD_L_STICK_Y:1, PAD_R_STICK_X:2, PAD_R_STICK_Y:3};
  pc.extend(pc, enums);
  pc.input = {};
  pc.extend(pc.input, enums);
})();
pc.extend(pc, function() {
  var MouseEvent = function(mouse, event) {
    var coords = {x:0, y:0};
    if (event) {
      if (event instanceof MouseEvent) {
        throw Error("Expected MouseEvent");
      }
      coords = mouse._getTargetCoords(event);
    } else {
      event = {};
    }
    if (coords) {
      this.x = coords.x;
      this.y = coords.y;
    } else {
      if (pc.Mouse.isPointerLocked()) {
        this.x = 0;
        this.y = 0;
      } else {
        return;
      }
    }
    if (event.detail) {
      this.wheel = -1 * event.detail;
    } else {
      if (event.wheelDelta) {
        this.wheel = event.wheelDelta / 120;
      } else {
        this.wheel = 0;
      }
    }
    if (pc.Mouse.isPointerLocked()) {
      this.dx = event.movementX || event.webkitMovementX || event.mozMovementX || 0;
      this.dy = event.movementY || event.webkitMovementY || event.mozMovementY || 0;
    } else {
      this.dx = this.x - mouse._lastX;
      this.dy = this.y - mouse._lastY;
    }
    if (event.type === "mousedown" || event.type === "mouseup") {
      this.button = event.button;
    } else {
      this.button = pc.MOUSEBUTTON_NONE;
    }
    this.buttons = mouse._buttons.slice(0);
    this.element = event.target;
    this.ctrlKey = event.ctrlKey || false;
    this.altKey = event.altKey || false;
    this.shiftKey = event.shiftKey || false;
    this.metaKey = event.metaKey || false;
    this.event = event;
  };
  var Mouse = function(element) {
    this._lastX = 0;
    this._lastY = 0;
    this._buttons = [false, false, false];
    this._lastbuttons = [false, false, false];
    this._upHandler = this._handleUp.bind(this);
    this._downHandler = this._handleDown.bind(this);
    this._moveHandler = this._handleMove.bind(this);
    this._wheelHandler = this._handleWheel.bind(this);
    this._contextMenuHandler = function(event) {
      event.preventDefault();
    };
    this._target = null;
    this._attached = false;
    this.attach(element);
    pc.events.attach(this);
  };
  Mouse.isPointerLocked = function() {
    return !!(document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement);
  };
  Mouse.prototype = {attach:function(element) {
    this._target = element;
    if (this._attached) {
      return;
    }
    this._attached = true;
    window.addEventListener("mouseup", this._upHandler, false);
    window.addEventListener("mousedown", this._downHandler, false);
    window.addEventListener("mousemove", this._moveHandler, false);
    window.addEventListener("mousewheel", this._wheelHandler, false);
    window.addEventListener("DOMMouseScroll", this._wheelHandler, false);
  }, detach:function() {
    if (!this._attached) {
      return;
    }
    this._attached = false;
    window.removeEventListener("mouseup", this._upHandler);
    window.removeEventListener("mousedown", this._downHandler);
    window.removeEventListener("mousemove", this._moveHandler);
    window.removeEventListener("mousewheel", this._wheelHandler);
    window.removeEventListener("DOMMouseScroll", this._wheelHandler);
  }, disableContextMenu:function() {
    if (!this._target) {
      return;
    }
    this._target.addEventListener("contextmenu", this._contextMenuHandler);
  }, enableContextMenu:function() {
    if (!this._target) {
      return;
    }
    this._target.removeEventListener("contextmenu", this._contextMenuHandler);
  }, enablePointerLock:function(success, error) {
    if (!document.body.requestPointerLock) {
      if (error) {
        error();
      }
      return;
    }
    var s = function() {
      success();
      document.removeEventListener("pointerlockchange", s);
    };
    var e = function() {
      error();
      document.removeEventListener("pointerlockerror", e);
    };
    if (success) {
      document.addEventListener("pointerlockchange", s, false);
    }
    if (error) {
      document.addEventListener("pointerlockerror", e, false);
    }
    document.body.requestPointerLock();
  }, disablePointerLock:function(success) {
    if (!document.exitPointerLock) {
      return;
    }
    var s = function() {
      success();
      document.removeEventListener("pointerlockchange", s);
    };
    if (success) {
      document.addEventListener("pointerlockchange", s, false);
    }
    document.exitPointerLock();
  }, update:function(dt) {
    this._lastbuttons[0] = this._buttons[0];
    this._lastbuttons[1] = this._buttons[1];
    this._lastbuttons[2] = this._buttons[2];
  }, isPressed:function(button) {
    return this._buttons[button];
  }, wasPressed:function(button) {
    return this._buttons[button] && !this._lastbuttons[button];
  }, wasReleased:function(button) {
    return !this._buttons[button] && this._lastbuttons[button];
  }, _handleUp:function(event) {
    this._buttons[event.button] = false;
    var e = new MouseEvent(this, event);
    if (!e.event) {
      return;
    }
    this.fire(pc.EVENT_MOUSEUP, e);
  }, _handleDown:function(event) {
    this._buttons[event.button] = true;
    var e = new MouseEvent(this, event);
    if (!e.event) {
      return;
    }
    this.fire(pc.EVENT_MOUSEDOWN, e);
  }, _handleMove:function(event) {
    var e = new MouseEvent(this, event);
    if (!e.event) {
      return;
    }
    this.fire(pc.EVENT_MOUSEMOVE, e);
    this._lastX = e.x;
    this._lastY = e.y;
  }, _handleWheel:function(event) {
    var e = new MouseEvent(this, event);
    if (!e.event) {
      return;
    }
    this.fire(pc.EVENT_MOUSEWHEEL, e);
  }, _getTargetCoords:function(event) {
    var rect = this._target.getBoundingClientRect();
    var left = Math.floor(rect.left);
    var top = Math.floor(rect.top);
    if (event.clientX < left || event.clientX >= left + this._target.clientWidth || event.clientY < top || event.clientY >= top + this._target.clientHeight) {
      return null;
    }
    return {x:event.clientX - left, y:event.clientY - top};
  }};
  (function() {
    if (typeof navigator === "undefined" || typeof document === "undefined") {
      return;
    }
    navigator.pointer = navigator.pointer || navigator.webkitPointer || navigator.mozPointer;
    var pointerlockchange = function() {
      var e = document.createEvent("CustomEvent");
      e.initCustomEvent("pointerlockchange", true, false, null);
      document.dispatchEvent(e);
    };
    var pointerlockerror = function() {
      var e = document.createEvent("CustomEvent");
      e.initCustomEvent("pointerlockerror", true, false, null);
      document.dispatchEvent(e);
    };
    document.addEventListener("webkitpointerlockchange", pointerlockchange, false);
    document.addEventListener("webkitpointerlocklost", pointerlockchange, false);
    document.addEventListener("mozpointerlockchange", pointerlockchange, false);
    document.addEventListener("mozpointerlocklost", pointerlockchange, false);
    document.addEventListener("webkitpointerlockerror", pointerlockerror, false);
    document.addEventListener("mozpointerlockerror", pointerlockerror, false);
    if (Element.prototype.mozRequestPointerLock) {
      Element.prototype.requestPointerLock = function() {
        this.mozRequestPointerLock();
      };
    } else {
      Element.prototype.requestPointerLock = Element.prototype.requestPointerLock || Element.prototype.webkitRequestPointerLock || Element.prototype.mozRequestPointerLock;
    }
    if (!Element.prototype.requestPointerLock && navigator.pointer) {
      Element.prototype.requestPointerLock = function() {
        var el = this;
        document.pointerLockElement = el;
        navigator.pointer.lock(el, pointerlockchange, pointerlockerror);
      };
    }
    document.exitPointerLock = document.exitPointerLock || document.webkitExitPointerLock || document.mozExitPointerLock;
    if (!document.exitPointerLock) {
      document.exitPointerLock = function() {
        if (navigator.pointer) {
          document.pointerLockElement = null;
          navigator.pointer.unlock();
        }
      };
    }
  })();
  return {Mouse:Mouse, MouseEvent:MouseEvent};
}());
pc.extend(pc, function() {
  var KeyboardEvent = function(keyboard, event) {
    if (event) {
      this.key = event.keyCode;
      this.element = event.target;
      this.event = event;
    } else {
      this.key = null;
      this.element = null;
      this.event = null;
    }
  };
  var _keyboardEvent = new KeyboardEvent;
  function makeKeyboardEvent(event) {
    _keyboardEvent.key = event.keyCode;
    _keyboardEvent.element = event.target;
    _keyboardEvent.event = event;
    return _keyboardEvent;
  }
  function toKeyCode(s) {
    if (typeof s == "string") {
      return s.toUpperCase().charCodeAt(0);
    } else {
      return s;
    }
  }
  var _keyCodeToKeyIdentifier = {9:"Tab", 13:"Enter", 16:"Shift", 17:"Control", 18:"Alt", 27:"Escape", 37:"Left", 38:"Up", 39:"Right", 40:"Down", 46:"Delete", 91:"Win"};
  var Keyboard = function(element, options) {
    options = options || {};
    this._element = null;
    this._keyDownHandler = this._handleKeyDown.bind(this);
    this._keyUpHandler = this._handleKeyUp.bind(this);
    this._keyPressHandler = this._handleKeyPress.bind(this);
    pc.events.attach(this);
    this._keymap = {};
    this._lastmap = {};
    if (element) {
      this.attach(element);
    }
    this.preventDefault = options.preventDefault || false;
    this.stopPropagation = options.stopPropagation || false;
  };
  Keyboard.prototype.attach = function(element) {
    if (this._element) {
      this.detach();
    }
    this._element = element;
    this._element.addEventListener("keydown", this._keyDownHandler, false);
    this._element.addEventListener("keypress", this._keyPressHandler, false);
    this._element.addEventListener("keyup", this._keyUpHandler, false);
  };
  Keyboard.prototype.detach = function() {
    this._element.removeEventListener("keydown", this._keyDownHandler);
    this._element.removeEventListener("keypress", this._keyPressHandler);
    this._element.removeEventListener("keyup", this._keyUpHandler);
    this._element = null;
  };
  Keyboard.prototype.toKeyIdentifier = function(keyCode) {
    keyCode = toKeyCode(keyCode);
    var count;
    var hex;
    var length;
    var id = _keyCodeToKeyIdentifier[keyCode.toString()];
    if (id) {
      return id;
    }
    hex = keyCode.toString(16).toUpperCase();
    length = hex.length;
    for (count = 0;count < 4 - length;count++) {
      hex = "0" + hex;
    }
    return "U+" + hex;
  };
  Keyboard.prototype._handleKeyDown = function(event) {
    var code = event.keyCode || event.charCode;
    var id = this.toKeyIdentifier(code);
    this._keymap[id] = true;
    this.fire("keydown", makeKeyboardEvent(event));
    if (this.preventDefault) {
      event.preventDefault();
    }
    if (this.stopPropagation) {
      event.stopPropagation();
    }
  };
  Keyboard.prototype._handleKeyUp = function(event) {
    var code = event.keyCode || event.charCode;
    var id = this.toKeyIdentifier(code);
    delete this._keymap[id];
    this.fire("keyup", makeKeyboardEvent(event));
    if (this.preventDefault) {
      event.preventDefault();
    }
    if (this.stopPropagation) {
      event.stopPropagation();
    }
  };
  Keyboard.prototype._handleKeyPress = function(event) {
    var code = event.keyCode || event.charCode;
    var id = this.toKeyIdentifier(code);
    this.fire("keypress", makeKeyboardEvent(event));
    if (this.preventDefault) {
      event.preventDefault();
    }
    if (this.stopPropagation) {
      event.stopPropagation();
    }
  };
  Keyboard.prototype.update = function(dt) {
    var prop;
    for (prop in this._lastmap) {
      delete this._lastmap[prop];
    }
    for (prop in this._keymap) {
      if (this._keymap.hasOwnProperty(prop)) {
        this._lastmap[prop] = this._keymap[prop];
      }
    }
  };
  Keyboard.prototype.isPressed = function(key) {
    var keyCode = toKeyCode(key);
    var id = this.toKeyIdentifier(keyCode);
    return !!this._keymap[id];
  };
  Keyboard.prototype.wasPressed = function(key) {
    var keyCode = toKeyCode(key);
    var id = this.toKeyIdentifier(keyCode);
    return !!this._keymap[id] && !!!this._lastmap[id];
  };
  Keyboard.prototype.wasReleased = function(key) {
    var keyCode = toKeyCode(key);
    var id = this.toKeyIdentifier(keyCode);
    return !!!this._keymap[id] && !!this._lastmap[id];
  };
  return {Keyboard:Keyboard};
}());
pc.extend(pc, function() {
  var GamePads = function() {
    this.gamepadsSupported = !!navigator.getGamepads || !!navigator.webkitGetGamepads;
    this.current = [];
    this.previous = [];
    this.deadZone = .25;
  };
  var MAPS = {DEFAULT:{buttons:["PAD_FACE_1", "PAD_FACE_2", "PAD_FACE_3", "PAD_FACE_4", "PAD_L_SHOULDER_1", "PAD_R_SHOULDER_1", "PAD_L_SHOULDER_2", "PAD_R_SHOULDER_2", "PAD_SELECT", "PAD_START", "PAD_L_STICK_BUTTON", "PAD_R_STICK_BUTTON", "PAD_UP", "PAD_DOWN", "PAD_LEFT", "PAD_RIGHT", "PAD_VENDOR"], axes:["PAD_L_STICK_X", "PAD_L_STICK_Y", "PAD_R_STICK_X", "PAD_R_STICK_Y"]}, PS3:{buttons:["PAD_FACE_1", "PAD_FACE_2", "PAD_FACE_4", "PAD_FACE_3", "PAD_L_SHOULDER_1", "PAD_R_SHOULDER_1", "PAD_L_SHOULDER_2", 
  "PAD_R_SHOULDER_2", "PAD_SELECT", "PAD_START", "PAD_L_STICK_BUTTON", "PAD_R_STICK_BUTTON", "PAD_UP", "PAD_DOWN", "PAD_LEFT", "PAD_RIGHT", "PAD_VENDOR"], axes:["PAD_L_STICK_X", "PAD_L_STICK_Y", "PAD_R_STICK_X", "PAD_R_STICK_Y"]}};
  var PRODUCT_CODES = {"Product: 0268":"PS3"};
  GamePads.prototype = {update:function(dt) {
    var pads = this.poll();
    var i, len = pads.length;
    for (i = 0;i < len;i++) {
      this.previous[i] = this.current[i];
      this.current[i] = pads[i];
    }
  }, poll:function() {
    var pads = [];
    if (this.gamepadsSupported) {
      var padDevices = navigator.getGamepads ? navigator.getGamepads() : navigator.webkitGetGamepads();
      var i, len = padDevices.length;
      for (i = 0;i < len;i++) {
        if (padDevices[i]) {
          pads.push({map:this.getMap(padDevices[i]), pad:padDevices[i]});
        }
      }
    }
    return pads;
  }, getMap:function(pad) {
    for (var code in PRODUCT_CODES) {
      if (pad.id.indexOf(code) >= 0) {
        return MAPS[PRODUCT_CODES[code]];
      }
    }
    return MAPS.DEFAULT;
  }, isPressed:function(index, button) {
    if (!this.current[index]) {
      return false;
    }
    var key = this.current[index].map.buttons[button];
    return this.current[index].pad.buttons[pc[key]].pressed;
  }, wasPressed:function(index, button) {
    if (!this.current[index]) {
      return false;
    }
    var key = this.current[index].map.buttons[button];
    var i = pc[key];
    return this.current[index].pad.buttons[i].pressed && !this.previous[index].pad.buttons[i].pressed;
  }, getAxis:function(index, axes) {
    if (!this.current[index]) {
      return false;
    }
    var key = this.current[index].map.axes[axes];
    var value = this.current[index].pad.axes[pc[key]];
    if (Math.abs(value) < this.deadZone) {
      value = 0;
    }
    return value;
  }};
  return {GamePads:GamePads};
}());
pc.extend(pc, function() {
  var TouchEvent = function(device, event) {
    this.element = event.target;
    this.event = event;
    this.touches = [];
    this.changedTouches = [];
    if (event) {
      var i, l = event.touches.length;
      for (i = 0;i < l;i++) {
        this.touches.push(new Touch(event.touches[i]));
      }
      l = event.changedTouches.length;
      for (i = 0;i < l;i++) {
        this.changedTouches.push(new Touch(event.changedTouches[i]));
      }
    }
  };
  TouchEvent.prototype = {getTouchById:function(id, list) {
    var i, l = list.length;
    for (i = 0;i < l;i++) {
      if (list[i].id === id) {
        return list[i];
      }
    }
    return null;
  }};
  var Touch = function(touch) {
    var coords = pc.getTouchTargetCoords(touch);
    this.id = touch.identifier;
    this.x = coords.x;
    this.y = coords.y;
    this.target = touch.target;
    this.touch = touch;
  };
  var TouchDevice = function(element) {
    this._startHandler = this._handleTouchStart.bind(this);
    this._endHandler = this._handleTouchEnd.bind(this);
    this._moveHandler = this._handleTouchMove.bind(this);
    this._cancelHandler = this._handleTouchCancel.bind(this);
    this.attach(element);
    pc.events.attach(this);
  };
  TouchDevice.prototype = {attach:function(element) {
    if (this._element) {
      this.detach();
    }
    this._element = element;
    this._element.addEventListener("touchstart", this._startHandler, false);
    this._element.addEventListener("touchend", this._endHandler, false);
    this._element.addEventListener("touchmove", this._moveHandler, false);
    this._element.addEventListener("touchcancel", this._cancelHandler, false);
  }, detach:function() {
    if (this._element) {
      this._element.removeEventListener("touchstart", this._startHandler, false);
      this._element.removeEventListener("touchend", this._endHandler, false);
      this._element.removeEventListener("touchmove", this._moveHandler, false);
      this._element.removeEventListener("touchcancel", this._cancelHandler, false);
    }
    this._element = null;
  }, _handleTouchStart:function(e) {
    this.fire("touchstart", new TouchEvent(this, e));
  }, _handleTouchEnd:function(e) {
    this.fire("touchend", new TouchEvent(this, e));
  }, _handleTouchMove:function(e) {
    e.preventDefault();
    this.fire("touchmove", new TouchEvent(this, e));
  }, _handleTouchCancel:function(e) {
    this.fire("touchcancel", new TouchEvent(this, e));
  }};
  return {getTouchTargetCoords:function(touch) {
    var totalOffsetX = 0;
    var totalOffsetY = 0;
    var canvasX = 0;
    var canvasY = 0;
    var target = touch.target;
    while (!(target instanceof HTMLElement)) {
      target = target.parentNode;
    }
    var currentElement = target;
    do {
      totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
      totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
      currentElement = currentElement.offsetParent;
    } while (currentElement);
    return {x:touch.pageX - totalOffsetX, y:touch.pageY - totalOffsetY};
  }, TouchDevice:TouchDevice};
}());
pc.extend(pc, function() {
  var Controller = function(element, options) {
    options = options || {};
    this._keyboard = options.keyboard || null;
    this._mouse = options.mouse || null;
    this._gamepads = options.gamepads || null;
    this._element = null;
    this._actions = {};
    this._axes = {};
    this._axesValues = {};
    if (element) {
      this.attach(element);
    }
  };
  Controller.prototype.attach = function(element) {
    this._element = element;
    if (this._keyboard) {
      this._keyboard.attach(element);
    }
    if (this._mouse) {
      this._mouse.attach(element);
    }
  };
  Controller.prototype.detach = function() {
    if (this._keyboard) {
      this._keyboard.detach();
    }
    if (this._mouse) {
      this._mouse.detach();
    }
    this._element = null;
  };
  Controller.prototype.disableContextMenu = function() {
    if (!this._mouse) {
      this._enableMouse();
    }
    this._mouse.disableContextMenu();
  };
  Controller.prototype.enableContextMenu = function() {
    if (!this._mouse) {
      this._enableMouse();
    }
    this._mouse.enableContextMenu();
  };
  Controller.prototype.update = function(dt) {
    if (this._keyboard) {
      this._keyboard.update(dt);
    }
    if (this._mouse) {
      this._mouse.update(dt);
    }
    if (this._gamepads) {
      this._gamepads.update(dt);
    }
    this._axesValues = {};
    for (var key in this._axes) {
      this._axesValues[key] = [];
    }
  };
  Controller.prototype.registerKeys = function(action, keys) {
    if (!this._keyboard) {
      this._enableKeyboard();
    }
    if (this._actions[action]) {
      throw new Error(pc.string.format("Action: {0} already registered", action));
    }
    if (keys === undefined) {
      throw new Error("Invalid button");
    }
    if (!keys.length) {
      keys = [keys];
    }
    if (this._actions[action]) {
      this._actions[action].push({type:pc.ACTION_KEYBOARD, keys:keys});
    } else {
      this._actions[action] = [{type:pc.ACTION_KEYBOARD, keys:keys}];
    }
  };
  Controller.prototype.registerMouse = function(action, button) {
    if (!this._mouse) {
      this._enableMouse();
    }
    if (button === undefined) {
      throw new Error("Invalid button");
    }
    if (this._actions[action]) {
      this._actions[action].push({type:pc.ACTION_MOUSE, button:button});
    } else {
      this._actions[action] = [{type:pc.ACTION_MOUSE, button:-button}];
    }
  };
  Controller.prototype.registerPadButton = function(action, pad, button) {
    if (button === undefined) {
      throw new Error("Invalid button");
    }
    if (this._actions[action]) {
      this._actions[action].push({type:pc.ACTION_GAMEPAD, button:button, pad:pad});
    } else {
      this._actions[action] = [{type:pc.ACTION_GAMEPAD, button:button, pad:pad}];
    }
  };
  Controller.prototype.registerAxis = function(options) {
    var name = options.name;
    if (!this._axes[name]) {
      this._axes[name] = [];
    }
    var i = this._axes[name].push(name);
    options = options || {};
    options.pad = options.pad || pc.PAD_1;
    var bind = function(controller, source, value, key) {
      switch(source) {
        case "mousex":
          controller._mouse.on(pc.EVENT_MOUSEMOVE, function(e) {
            controller._axesValues[name][i] = e.dx / 10;
          });
          break;
        case "mousey":
          controller._mouse.on(pc.EVENT_MOUSEMOVE, function(e) {
            controller._axesValues[name][i] = e.dy / 10;
          });
          break;
        case "key":
          controller._axes[name].push(function() {
            return controller._keyboard.isPressed(key) ? value : 0;
          });
          break;
        case "padrx":
          controller._axes[name].push(function() {
            return controller._gamepads.getAxis(options.pad, pc.PAD_R_STICK_X);
          });
          break;
        case "padry":
          controller._axes[name].push(function() {
            return controller._gamepads.getAxis(options.pad, pc.PAD_R_STICK_Y);
          });
          break;
        case "padlx":
          controller._axes[name].push(function() {
            return controller._gamepads.getAxis(options.pad, pc.PAD_L_STICK_X);
          });
          break;
        case "padly":
          controller._axes[name].push(function() {
            return controller._gamepads.getAxis(options.pad, pc.PAD_L_STICK_Y);
          });
          break;
        default:
          throw new Error("Unknown axis");
      }
    };
    bind(this, options.positive, 1, options.positiveKey);
    if (options.negativeKey || options.negative !== options.positive) {
      bind(this, options.negative, -1, options.negativeKey);
    }
  };
  Controller.prototype.isPressed = function(actionName) {
    if (!this._actions[actionName]) {
      return false;
    }
    var action;
    var index = 0;
    var length = this._actions[actionName].length;
    for (index = 0;index < length;++index) {
      action = this._actions[actionName][index];
      switch(action.type) {
        case pc.ACTION_KEYBOARD:
          if (this._keyboard) {
            var i, len = action.keys.length;
            for (i = 0;i < len;i++) {
              if (this._keyboard.isPressed(action.keys[i])) {
                return true;
              }
            }
          }
          break;
        case pc.ACTION_MOUSE:
          if (this._mouse && this._mouse.isPressed(action.button)) {
            return true;
          }
          break;
        case pc.ACTION_GAMEPAD:
          if (this._gamepads && this._gamepads.isPressed(action.pad, action.button)) {
            return true;
          }
          break;
      }
    }
    return false;
  };
  Controller.prototype.wasPressed = function(actionName) {
    if (!this._actions[actionName]) {
      return false;
    }
    var index = 0;
    var length = this._actions[actionName].length;
    for (index = 0;index < length;++index) {
      var action = this._actions[actionName][index];
      switch(action.type) {
        case pc.ACTION_KEYBOARD:
          if (this._keyboard) {
            var i, len = action.keys.length;
            for (i = 0;i < len;i++) {
              if (this._keyboard.wasPressed(action.keys[i])) {
                return true;
              }
            }
          }
          break;
        case pc.ACTION_MOUSE:
          if (this._mouse && this._mouse.wasPressed(action.button)) {
            return true;
          }
          break;
        case pc.ACTION_GAMEPAD:
          if (this._gamepads && this._gamepads.wasPressed(action.pad, action.button)) {
            return true;
          }
          break;
      }
    }
    return false;
  };
  Controller.prototype.getAxis = function(name) {
    var value = 0;
    if (this._axes[name]) {
      var i, len = this._axes[name].length;
      for (i = 0;i < len;i++) {
        if (pc.type(this._axes[name][i]) === "function") {
          var v = this._axes[name][i]();
          if (Math.abs(v) > Math.abs(value)) {
            value = v;
          }
        } else {
          if (this._axesValues[name]) {
            if (Math.abs(this._axesValues[name][i]) > Math.abs(value)) {
              value = this._axesValues[name][i];
            }
          }
        }
      }
    }
    return value;
  };
  Controller.prototype._enableMouse = function() {
    this._mouse = new pc.Mouse;
    if (!this._element) {
      throw new Error("Controller must be attached to an Element");
    }
    this._mouse.attach(this._element);
  };
  Controller.prototype._enableKeyboard = function() {
    this._keyboard = new pc.Keyboard;
    if (!this._element) {
      throw new Error("Controller must be attached to an Element");
    }
    this._keyboard.attach(this._element);
  };
  return {Controller:Controller};
}());
pc.extend(pc, function() {
  var VrManager = function(app) {
    pc.events.attach(this);
    var self = this;
    this.isSupported = VrManager.isSupported;
    this.usesPolyfill = VrManager.usesPolyfill;
    if (window.InitializeWebVRPolyfill) {
      window.InitializeWebVRPolyfill();
    }
    this._index = {};
    this.displays = [];
    this.display = null;
    this._app = app;
    this._onDisplayConnect = this._onDisplayConnect.bind(this);
    this._onDisplayDisconnect = this._onDisplayDisconnect.bind(this);
    self._attach();
    this._getDisplays(function(err, displays) {
      if (err) {
        self.fire("error", err);
      } else {
        for (var i = 0;i < displays.length;i++) {
          self._addDisplay(displays[i]);
        }
        self.fire("ready", self.displays);
      }
    });
  };
  VrManager.isSupported = !!navigator.getVRDisplays;
  VrManager.usesPolyfill = !!window.InitializeWebVRPolyfill;
  VrManager.prototype = {_attach:function() {
    window.addEventListener("vrdisplayconnect", this._onDisplayConnect);
    window.addEventListener("vrdisplaydisconnect", this._onDisplayDisconnect);
  }, _detach:function() {
    window.removeEventListener("vrdisplayconnect", this._onDisplayConnect);
    window.removeEventListener("vrdisplaydisconnect", this._onDisplayDisconnect);
  }, destroy:function() {
    this._detach();
  }, poll:function() {
    var l = this.displays.length;
    if (!l) {
      return;
    }
    for (var i = 0;i < l;i++) {
      if (this.displays[i]._camera) {
        this.displays[i].poll();
      }
    }
  }, _getDisplays:function(callback) {
    var self = this;
    if (navigator.getVRDisplays) {
      navigator.getVRDisplays().then(function(displays) {
        if (callback) {
          callback(null, displays);
        }
      });
    } else {
      if (callback) {
        callback(new Error("WebVR not supported"));
      }
    }
  }, _addDisplay:function(vrDisplay) {
    if (this._index[vrDisplay.displayId]) {
      return;
    }
    var display = new pc.VrDisplay(this._app, vrDisplay);
    this._index[display.id] = display;
    this.displays.push(display);
    if (!this.display) {
      this.display = display;
    }
    this.fire("displayconnect", display);
  }, _onDisplayConnect:function(e) {
    this._addDisplay(e.display);
  }, _onDisplayDisconnect:function(e) {
    var display = this._index[e.display.displayId];
    if (!display) {
      return;
    }
    display.destroy();
    delete this._index[display.id];
    var ind = this.displays.indexOf(display);
    this.displays.splice(ind, 1);
    if (this.display === display) {
      if (this.displays.length) {
        this.display = this.displays[0];
      } else {
        this.display = null;
      }
    }
    this.fire("displaydisconnect", display);
  }};
  return {VrManager:VrManager};
}());
pc.extend(pc, function() {
  var VrDisplay = function(app, display) {
    var self = this;
    this._app = app;
    this._device = app.graphicsDevice;
    this.id = display.displayId;
    this._frameData = null;
    if (window.VRFrameData) {
      this._frameData = new window.VRFrameData;
    }
    this.display = display;
    this._camera = null;
    this.sitToStandInv = new pc.Mat4;
    this.leftView = new pc.Mat4;
    this.leftProj = new pc.Mat4;
    this.leftViewInv = new pc.Mat4;
    this.leftPos = new pc.Vec3;
    this.rightView = new pc.Mat4;
    this.rightProj = new pc.Mat4;
    this.rightViewInv = new pc.Mat4;
    this.rightPos = new pc.Vec3;
    this.combinedPos = new pc.Vec3;
    this.combinedView = new pc.Mat4;
    this.combinedProj = new pc.Mat4;
    this.combinedViewInv = new pc.Mat4;
    this.combinedFov = 0;
    this.combinedAspect = 0;
    this.presenting = false;
    self._presentChange = function(event) {
      var display;
      if (event.display) {
        display = event.display;
      } else {
        if (event.detail && event.detail.display) {
          display = event.detail.display;
        } else {
          if (event.detail && event.detail.vrdisplay) {
            display = event.detail.vrdisplay;
          }
        }
      }
      if (display === self.display) {
        self.presenting = self.display && self.display.isPresenting;
        if (self.presenting) {
          var leftEye = self.display.getEyeParameters("left");
          var rightEye = self.display.getEyeParameters("right");
          var w = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2;
          var h = Math.max(leftEye.renderHeight, rightEye.renderHeight);
          self._app.graphicsDevice.setResolution(w, h);
          self._app._allowResize = false;
        } else {
          self._app.setCanvasResolution(pc.RESOLUTION_AUTO);
          self._app._allowResize = true;
        }
        self.fire("beforepresentchange", self);
        self.fire("presentchange", self);
      }
    };
    window.addEventListener("vrdisplaypresentchange", self._presentChange, false);
    pc.events.attach(this);
  };
  VrDisplay.prototype = {destroy:function() {
    window.removeEventListener("vrdisplaypresentchange", self._presentChange);
    if (this._camera) {
      this._camera.vrDisplay = null;
    }
    this._camera = null;
  }, poll:function() {
    if (this.display) {
      this.display.getFrameData(this._frameData);
      this.leftProj.data = this._frameData.leftProjectionMatrix;
      this.rightProj.data = this._frameData.rightProjectionMatrix;
      var stage = this.display.stageParameters;
      if (stage) {
        this.sitToStandInv.set(stage.sittingToStandingTransform).invert();
        this.combinedView.set(this._frameData.leftViewMatrix);
        this.leftView.mul2(this.combinedView, this.sitToStandInv);
        this.combinedView.set(this._frameData.rightViewMatrix);
        this.rightView.mul2(this.combinedView, this.sitToStandInv);
      } else {
        this.leftView.set(this._frameData.leftViewMatrix);
        this.rightView.set(this._frameData.rightViewMatrix);
      }
      var nx = this.leftProj.data[3] + this.leftProj.data[0];
      var nz = this.leftProj.data[11] + this.leftProj.data[8];
      var l = 1 / Math.sqrt(nx * nx + nz * nz);
      nx *= l;
      nz *= l;
      var maxFov = -Math.atan2(nz, nx);
      nx = this.rightProj.data[3] + this.rightProj.data[0];
      nz = this.rightProj.data[11] + this.rightProj.data[8];
      l = 1 / Math.sqrt(nx * nx + nz * nz);
      nx *= l;
      nz *= l;
      maxFov = Math.max(maxFov, -Math.atan2(nz, nx));
      maxFov *= 2;
      this.combinedFov = maxFov;
      var aspect = this.rightProj.data[5] / this.rightProj.data[0];
      this.combinedAspect = aspect;
      var view = this.combinedView;
      view.copy(this.leftView);
      view.invert();
      this.leftViewInv.copy(view);
      var pos = this.combinedPos.data;
      pos[0] = this.leftPos.data[0] = view.data[12];
      pos[1] = this.leftPos.data[1] = view.data[13];
      pos[2] = this.leftPos.data[2] = view.data[14];
      view.copy(this.rightView);
      view.invert();
      this.rightViewInv.copy(view);
      var deltaX = pos[0] - view.data[12];
      var deltaY = pos[1] - view.data[13];
      var deltaZ = pos[2] - view.data[14];
      var dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);
      this.rightPos.data[0] = view.data[12];
      this.rightPos.data[1] = view.data[13];
      this.rightPos.data[2] = view.data[14];
      pos[0] += view.data[12];
      pos[1] += view.data[13];
      pos[2] += view.data[14];
      pos[0] *= .5;
      pos[1] *= .5;
      pos[2] *= .5;
      var b = Math.PI * .5;
      var c = maxFov * .5;
      var a = Math.PI - (b + c);
      var offset = dist * .5 * Math.sin(a);
      var fwdX = view.data[8];
      var fwdY = view.data[9];
      var fwdZ = view.data[10];
      view.data[12] = pos[0] + fwdX * offset;
      view.data[13] = pos[1] + fwdY * offset;
      view.data[14] = pos[2] + fwdZ * offset;
      this.combinedViewInv.copy(view);
      view.invert();
      this.combinedProj.setPerspective(maxFov * pc.math.RAD_TO_DEG, aspect, this.display.depthNear + offset, this.display.depthFar + offset, true);
    }
  }, requestPresent:function(callback) {
    if (!this.display) {
      if (callback) {
        callback(new Error("No VrDisplay to requestPresent"));
      }
      return;
    }
    if (this.presenting) {
      if (callback) {
        callback(new Error("VrDisplay already presenting"));
      }
      return;
    }
    this.display.requestPresent([{source:this._device.canvas}]).then(function() {
      if (callback) {
        callback();
      }
    }, function(err) {
      if (callback) {
        callback(err);
      }
    });
  }, exitPresent:function(callback) {
    if (!this.display) {
      if (callback) {
        callback(new Error("No VrDisplay to exitPresent"));
      }
    }
    if (!this.presenting) {
      if (callback) {
        callback(new Error("VrDisplay not presenting"));
      }
      return;
    }
    this.display.exitPresent().then(function() {
      if (callback) {
        callback();
      }
    }, function() {
      if (callback) {
        callback(new Error("exitPresent failed"));
      }
    });
  }, requestAnimationFrame:function(fn) {
    if (this.display) {
      this.display.requestAnimationFrame(fn);
    }
  }, submitFrame:function() {
    if (this.display) {
      this.display.submitFrame();
    }
  }, reset:function() {
    if (this.display) {
      this.display.resetPose();
    }
  }, setClipPlanes:function(n, f) {
    if (this.display) {
      this.display.depthNear = n;
      this.display.depthFar = f;
    }
  }, getFrameData:function() {
    if (this.display) {
      return this._frameData;
    }
  }};
  Object.defineProperty(VrDisplay.prototype, "capabilities", {get:function() {
    if (this.display) {
      return this.display.capabilities;
    }
    return {};
  }});
  return {VrDisplay:VrDisplay};
}());
pc.extend(pc, function() {
  var Http = function Http() {
  };
  Http.ContentType = {FORM_URLENCODED:"application/x-www-form-urlencoded", GIF:"image/gif", JPEG:"image/jpeg", DDS:"image/dds", JSON:"application/json", PNG:"image/png", TEXT:"text/plain", XML:"application/xml", WAV:"audio/x-wav", OGG:"audio/ogg", MP3:"audio/mpeg", MP4:"audio/mp4", AAC:"audio/aac", BIN:"application/octet-stream"};
  Http.ResponseType = {TEXT:"text", ARRAY_BUFFER:"arraybuffer", BLOB:"blob", DOCUMENT:"document"};
  Http.binaryExtensions = [".model", ".wav", ".ogg", ".mp3", ".mp4", ".m4a", ".aac", ".dds"];
  Http.prototype = {ContentType:Http.ContentType, ResponseType:Http.ResponseType, binaryExtensions:Http.binaryExtensions, get:function(url, options, callback) {
    if (typeof options === "function") {
      callback = options;
      options = {};
    }
    return this.request("GET", url, options, callback);
  }, post:function(url, data, options, callback) {
    if (typeof options === "function") {
      callback = options;
      options = {};
    }
    options.postdata = data;
    return this.request("POST", url, options, callback);
  }, put:function(url, data, options, callback) {
    if (typeof options === "function") {
      callback = options;
      options = {};
    }
    options.postdata = data;
    return this.request("PUT", url, options, callback);
  }, del:function(url, options, callback) {
    if (typeof options === "function") {
      callback = options;
      options = {};
    }
    return this.request("DELETE", url, options, callback);
  }, request:function(method, url, options, callback) {
    var uri, query, timestamp, postdata, xhr;
    var errored = false;
    if (typeof options === "function") {
      callback = options;
      options = {};
    }
    options.callback = callback;
    if (options.async == null) {
      options.async = true;
    }
    if (options.headers == null) {
      options.headers = {};
    }
    if (options.postdata != null) {
      if (options.postdata instanceof Document) {
        postdata = options.postdata;
      } else {
        if (options.postdata instanceof FormData) {
          postdata = options.postdata;
        } else {
          if (options.postdata instanceof Object) {
            var contentType = options.headers["Content-Type"];
            if (contentType === undefined) {
              options.headers["Content-Type"] = Http.ContentType.FORM_URLENCODED;
              contentType = options.headers["Content-Type"];
            }
            switch(contentType) {
              case Http.ContentType.FORM_URLENCODED:
                postdata = "";
                var bFirstItem = true;
                for (var key in options.postdata) {
                  if (options.postdata.hasOwnProperty(key)) {
                    if (bFirstItem) {
                      bFirstItem = false;
                    } else {
                      postdata += "&";
                    }
                    postdata += escape(key) + "=" + escape(options.postdata[key]);
                  }
                }
                break;
              case Http.ContentType.JSON:
              default:
                if (contentType == null) {
                  options.headers["Content-Type"] = Http.ContentType.JSON;
                }
                postdata = JSON.stringify(options.postdata);
                break;
            }
          } else {
            postdata = options.postdata;
          }
        }
      }
    }
    if (!xhr) {
      xhr = new XMLHttpRequest;
    }
    if (options.cache === false) {
      timestamp = pc.time.now();
      uri = new pc.URI(url);
      if (!uri.query) {
        uri.query = "ts=" + timestamp;
      } else {
        uri.query = uri.query + "&ts=" + timestamp;
      }
      url = uri.toString();
    }
    if (options.query) {
      uri = new pc.URI(url);
      query = pc.extend(uri.getQuery(), options.query);
      uri.setQuery(query);
      url = uri.toString();
    }
    xhr.open(method, url, options.async);
    xhr.withCredentials = options.withCredentials !== undefined ? options.withCredentials : false;
    xhr.responseType = options.responseType || this._guessResponseType(url);
    for (var header in options.headers) {
      if (options.headers.hasOwnProperty(header)) {
        xhr.setRequestHeader(header, options.headers[header]);
      }
    }
    xhr.onreadystatechange = function() {
      this._onReadyStateChange(method, url, options, xhr);
    }.bind(this);
    xhr.onerror = function() {
      this._onError(method, url, options, xhr);
      errored = true;
    }.bind(this);
    try {
      xhr.send(postdata);
    } catch (e) {
      if (!errored) {
        options.error(xhr.status, xhr, e);
      }
    }
    return xhr;
  }, _guessResponseType:function(url) {
    var uri = new pc.URI(url);
    var ext = pc.path.getExtension(uri.path);
    if (Http.binaryExtensions.indexOf(ext) >= 0) {
      return Http.ResponseType.ARRAY_BUFFER;
    }
    return Http.ResponseType.TEXT;
  }, _isBinaryContentType:function(contentType) {
    var binTypes = [Http.ContentType.MP4, Http.ContentType.WAV, Http.ContentType.OGG, Http.ContentType.MP3, Http.ContentType.BIN, Http.ContentType.DDS];
    if (binTypes.indexOf(contentType) >= 0) {
      return true;
    }
    return false;
  }, _onReadyStateChange:function(method, url, options, xhr) {
    if (xhr.readyState === 4) {
      switch(xhr.status) {
        case 0:
          {
            if (url[0] != "/") {
              this._onSuccess(method, url, options, xhr);
            }
            break;
          }
        case 200:
        case 201:
        case 206:
        case 304:
          {
            this._onSuccess(method, url, options, xhr);
            break;
          }
        default:
          {
            this._onError(method, url, options, xhr);
            break;
          }
      }
    }
  }, _onSuccess:function(method, url, options, xhr) {
    var response;
    var header;
    var contentType;
    var parameter;
    var parts;
    header = xhr.getResponseHeader("Content-Type");
    if (header) {
      parts = header.split(";");
      contentType = parts[0].trim();
      if (parts[1]) {
        parameter = parts[1].trim();
      }
    }
    if (contentType === this.ContentType.JSON || url.split("?")[0].endsWith(".json")) {
      response = JSON.parse(xhr.responseText);
    } else {
      if (this._isBinaryContentType(contentType)) {
        response = xhr.response;
      } else {
        if (xhr.responseType === Http.ResponseType.ARRAY_BUFFER) {
          logWARNING(pc.string.format("responseType: {0} being served with Content-Type: {1}", Http.ResponseType.ARRAY_BUFFER, contentType));
          response = xhr.response;
        } else {
          if (xhr.responseType === Http.ResponseType.DOCUMENT || contentType === this.ContentType.XML) {
            response = xhr.responseXML;
          } else {
            response = xhr.responseText;
          }
        }
      }
    }
    options.callback(null, response);
  }, _onError:function(method, url, options, xhr) {
    options.callback(xhr.status, null);
  }};
  return {Http:Http, http:new Http};
}());
pc.extend(pc, function() {
  var ScriptRegistry = function(app) {
    pc.events.attach(this);
    this.app = app;
    this._scripts = {};
    this._list = [];
  };
  ScriptRegistry.prototype.add = function(script) {
    var self = this;
    if (this._scripts.hasOwnProperty(script.__name)) {
      setTimeout(function() {
        if (script.prototype.swap) {
          var old = self._scripts[script.__name];
          var ind = self._list.indexOf(old);
          self._list[ind] = script;
          self._scripts[script.__name] = script;
          self.fire("swap", script.__name, script);
          self.fire("swap:" + script.__name, script);
        } else {
          console.warn("script registry already has '" + script.__name + "' script, define 'swap' method for new script type to enable code hot swapping");
        }
      });
      return false;
    }
    this._scripts[script.__name] = script;
    this._list.push(script);
    this.fire("add", script.__name, script);
    this.fire("add:" + script.__name, script);
    setTimeout(function() {
      if (!self._scripts.hasOwnProperty(script.__name)) {
        return;
      }
      var components = self.app.systems.script._components;
      var i, s, scriptInstance, attributes;
      var scriptInstances = [];
      var scriptInstancesInitialized = [];
      for (i = 0;i < components.length;i++) {
        if (components[i]._scriptsIndex[script.__name] && components[i]._scriptsIndex[script.__name].awaiting) {
          if (components[i]._scriptsData && components[i]._scriptsData[script.__name]) {
            attributes = components[i]._scriptsData[script.__name].attributes;
          }
          scriptInstance = components[i].create(script.__name, {preloading:true, ind:components[i]._scriptsIndex[script.__name].ind, attributes:attributes});
          if (scriptInstance) {
            scriptInstances.push(scriptInstance);
          }
        }
      }
      for (i = 0;i < scriptInstances.length;i++) {
        scriptInstances[i].__initializeAttributes();
      }
      for (i = 0;i < scriptInstances.length;i++) {
        if (scriptInstances[i].enabled) {
          scriptInstances[i]._initialized = true;
          scriptInstancesInitialized.push(scriptInstances[i]);
          if (scriptInstances[i].initialize) {
            scriptInstances[i].initialize();
          }
        }
      }
      for (i = 0;i < scriptInstancesInitialized.length;i++) {
        scriptInstancesInitialized[i]._postInitialized = true;
        if (scriptInstancesInitialized[i].postInitialize) {
          scriptInstancesInitialized[i].postInitialize();
        }
      }
    });
    return true;
  };
  ScriptRegistry.prototype.remove = function(script) {
    var name = script;
    if (typeof script === "function") {
      name = script.__name;
    }
    if (!this._scripts.hasOwnProperty(name)) {
      return false;
    }
    var item = this._scripts[name];
    delete this._scripts[name];
    var ind = this._list.indexOf(item);
    this._list.splice(ind, 1);
    this.fire("remove", name, item);
    this.fire("remove:" + name, item);
    return true;
  };
  ScriptRegistry.prototype.get = function(name) {
    return this._scripts[name] || null;
  };
  ScriptRegistry.prototype.has = function(name) {
    return this._scripts.hasOwnProperty(name);
  };
  ScriptRegistry.prototype.list = function() {
    return this._list;
  };
  return {ScriptRegistry:ScriptRegistry};
}());
pc.extend(pc, function() {
  var rawToValue = function(app, args, value, old) {
    switch(args.type) {
      case "boolean":
        return !!value;
        break;
      case "number":
        if (typeof value === "number") {
          return value;
        } else {
          if (typeof value === "string") {
            var v = parseInt(value, 10);
            if (isNaN(v)) {
              return null;
            }
            return v;
          } else {
            if (typeof value === "boolean") {
              return 0 + value;
            } else {
              return null;
            }
          }
        }
        break;
      case "json":
        if (typeof value === "object") {
          return value;
        } else {
          try {
            return JSON.parse(value);
          } catch (ex) {
            return null;
          }
        }
        break;
      case "asset":
        if (args.array) {
          var result = [];
          if (value instanceof Array) {
            for (var i = 0;i < value.length;i++) {
              if (value[i] instanceof pc.Asset) {
                result.push(value[i]);
              } else {
                if (typeof value[i] === "number") {
                  result.push(app.assets.get(value[i]) || null);
                } else {
                  if (typeof value[i] === "string") {
                    result.push(app.assets.get(parseInt(value[i], 10)) || null);
                  } else {
                    result.push(null);
                  }
                }
              }
            }
          }
          return result;
        } else {
          if (value instanceof pc.Asset) {
            return value;
          } else {
            if (typeof value === "number") {
              return app.assets.get(value) || null;
            } else {
              if (typeof value === "string") {
                return app.assets.get(parseInt(value, 10)) || null;
              } else {
                return null;
              }
            }
          }
        }
        break;
      case "entity":
        if (value instanceof pc.GraphNode) {
          return value;
        } else {
          if (typeof value === "string") {
            return app.root.findByGuid(value);
          } else {
            return null;
          }
        }
        break;
      case "rgb":
      case "rgba":
        if (value instanceof pc.Color) {
          if (old instanceof pc.Color) {
            old.copy(value);
            return old;
          } else {
            return value.clone();
          }
        } else {
          if (value instanceof Array && value.length >= 3 && value.length <= 4) {
            for (var i = 0;i < value.length;i++) {
              if (typeof value[i] !== "number") {
                return null;
              }
            }
            if (!old) {
              old = new pc.Color;
            }
            for (var i = 0;i < 4;i++) {
              old.data[i] = i === 4 && value.length === 3 ? 1 : value[i];
            }
            return old;
          } else {
            if (typeof value === "string" && /#([0-9abcdef]{2}){3,4}/i.test(value)) {
              if (!old) {
                old = new pc.Color;
              }
              old.fromString(value);
              return old;
            } else {
              return null;
            }
          }
        }
        break;
      case "vec2":
      case "vec3":
      case "vec4":
        var len = parseInt(args.type.slice(3), 10);
        if (value instanceof pc["Vec" + len]) {
          if (old instanceof pc["Vec" + len]) {
            old.copy(value);
            return old;
          } else {
            return value.clone();
          }
        } else {
          if (value instanceof Array && value.length === len) {
            for (var i = 0;i < value.length;i++) {
              if (typeof value[i] !== "number") {
                return null;
              }
            }
            if (!old) {
              old = new pc["Vec" + len];
            }
            for (var i = 0;i < len;i++) {
              old.data[i] = value[i];
            }
            return old;
          } else {
            return null;
          }
        }
        break;
      case "curve":
        if (value) {
          var curve;
          if (value instanceof pc.Curve || value instanceof pc.CurveSet) {
            curve = value.clone();
          } else {
            var CurveType = value.keys[0] instanceof Array ? pc.CurveSet : pc.Curve;
            curve = new CurveType(value.keys);
            curve.type = value.type;
          }
          return curve;
        }
        break;
    }
    return value;
  };
  var ScriptAttributes = function(scriptType) {
    this.scriptType = scriptType;
    this.index = {};
  };
  ScriptAttributes.prototype.add = function(name, args) {
    if (this.index[name]) {
      console.warn("attribute '" + name + "' is already defined for script type '" + this.scriptType.name + "'");
      return;
    } else {
      if (pc.createScript.reservedAttributes[name]) {
        console.warn("attribute '" + name + "' is a reserved attribute name");
        return;
      }
    }
    this.index[name] = args;
    Object.defineProperty(this.scriptType.prototype, name, {get:function() {
      return this.__attributes[name];
    }, set:function(raw) {
      var old = this.__attributes[name];
      this.__attributes[name] = rawToValue(this.app, args, raw, old);
      this.fire("attr", name, this.__attributes[name], old);
      this.fire("attr:" + name, this.__attributes[name], old);
    }});
  };
  ScriptAttributes.prototype.remove = function(name) {
    if (!this.index[name]) {
      return false;
    }
    delete this.index[name];
    delete this.scriptType.prototype[name];
    return true;
  };
  ScriptAttributes.prototype.has = function(name) {
    return !!this.index[name];
  };
  ScriptAttributes.prototype.get = function(name) {
    return this.index[name] || null;
  };
  var createScript = function(name, app) {
    if (pc.script.legacy) {
      console.error("This project is using the legacy script system. You cannot call pc.createScript(). See: http://developer.playcanvas.com/en/user-manual/scripting/legacy/");
      return null;
    }
    if (createScript.reservedScripts[name]) {
      throw new Error("script name: '" + name + "' is reserved, please change script name");
    }
    var script = function(args) {
      if (!args || !args.app || !args.entity) {
        console.warn("script '" + name + "' has missing arguments in consructor");
      }
      pc.events.attach(this);
      this.app = args.app;
      this.entity = args.entity;
      this._enabled = typeof args.enabled === "boolean" ? args.enabled : true;
      this._enabledOld = this.enabled;
      this.__attributes = {};
      this.__attributesRaw = args.attributes || null;
      this.__scriptType = script;
    };
    script.__name = name;
    script.attributes = new ScriptAttributes(script);
    script.prototype.__initializeAttributes = function(force) {
      if (!force && !this.__attributesRaw) {
        return;
      }
      for (var key in script.attributes.index) {
        if (this.__attributesRaw && this.__attributesRaw.hasOwnProperty(key)) {
          this[key] = this.__attributesRaw[key];
        } else {
          if (!this.__attributes.hasOwnProperty(key)) {
            if (script.attributes.index[key].hasOwnProperty("default")) {
              this[key] = script.attributes.index[key]["default"];
            } else {
              this[key] = null;
            }
          }
        }
      }
      this.__attributesRaw = null;
    };
    script.extend = function(methods) {
      for (var key in methods) {
        if (!methods.hasOwnProperty(key)) {
          continue;
        }
        script.prototype[key] = methods[key];
      }
    };
    Object.defineProperty(script.prototype, "enabled", {get:function() {
      return this._enabled && this.entity.script.enabled && this.entity.enabled;
    }, set:function(value) {
      if (this._enabled !== !!value) {
        this._enabled = !!value;
      }
      if (this.enabled !== this._enabledOld) {
        this._enabledOld = this.enabled;
        this.fire(this.enabled ? "enable" : "disable");
        this.fire("state", this.enabled);
      }
    }});
    var registry = app ? app.scripts : pc.Application.getApplication().scripts;
    registry.add(script);
    pc.ScriptHandler._push(script);
    return script;
  };
  createScript.reservedScripts = ["system", "entity", "create", "destroy", "swap", "move", "scripts", "_scripts", "_scriptsIndex", "_scriptsData", "enabled", "_oldState", "onEnable", "onDisable", "onPostStateChange", "_onSetEnabled", "_checkState", "_onBeforeRemove", "_onInitializeAttributes", "_onInitialize", "_onPostInitialize", "_onUpdate", "_onPostUpdate", "_callbacks", "has", "on", "off", "fire", "once", "hasEvent"];
  var reservedScripts = {};
  for (var i = 0;i < createScript.reservedScripts.length;i++) {
    reservedScripts[createScript.reservedScripts[i]] = 1;
  }
  createScript.reservedScripts = reservedScripts;
  createScript.reservedAttributes = ["app", "entity", "enabled", "_enabled", "_enabledOld", "__attributes", "__attributesRaw", "__scriptType", "_callbacks", "has", "on", "off", "fire", "once", "hasEvent"];
  var reservedAttributes = {};
  for (var i = 0;i < createScript.reservedAttributes.length;i++) {
    reservedAttributes[createScript.reservedAttributes[i]] = 1;
  }
  createScript.reservedAttributes = reservedAttributes;
  return {createScript:createScript};
}());
pc.script = function() {
  var _main = null;
  var _loader = null;
  var _legacy = false;
  var _createdLoadingScreen = false;
  var script = {app:null, create:function(name, callback) {
    if (!_legacy) {
      return;
    }
    var ScriptType = callback(pc.script.app);
    ScriptType._pcScriptName = name;
    pc.ScriptHandler._push(ScriptType);
    this.fire("created", name, callback);
  }, attribute:function(name, type, defaultValue, options) {
  }, createLoadingScreen:function(callback) {
    if (_createdLoadingScreen) {
      return;
    }
    _createdLoadingScreen = true;
    var app = pc.Application.getApplication();
    callback(app);
  }};
  Object.defineProperty(script, "legacy", {get:function() {
    return _legacy;
  }, set:function(value) {
    _legacy = value;
  }});
  pc.events.attach(script);
  return script;
}();
pc.extend(pc, function() {
  var Application = function(canvas, options) {
    options = options || {};
    pc.log.open();
    pc.events.attach(this);
    Application._applications[canvas.id] = this;
    Application._currentApplication = this;
    this._time = 0;
    this.timeScale = 1;
    this._librariesLoaded = false;
    this._fillMode = pc.FILLMODE_KEEP_ASPECT;
    this._resolutionMode = pc.RESOLUTION_FIXED;
    this._allowResize = true;
    this.context = this;
    this.graphicsDevice = new pc.GraphicsDevice(canvas, options.graphicsDeviceOptions);
    this.stats = new pc.ApplicationStats(this.graphicsDevice);
    this.systems = new pc.ComponentSystemRegistry;
    this._audioManager = new pc.SoundManager(options);
    this.loader = new pc.ResourceLoader;
    this.scene = new pc.Scene;
    this.root = new pc.Entity(this);
    this.root._enabledInHierarchy = true;
    this._enableList = [];
    this._enableList.size = 0;
    this.assets = new pc.AssetRegistry(this.loader);
    if (options.assetPrefix) {
      this.assets.prefix = options.assetPrefix;
    }
    this.scriptsOrder = options.scriptsOrder || [];
    this.scripts = new pc.ScriptRegistry(this);
    this.renderer = new pc.ForwardRenderer(this.graphicsDevice);
    this.lightmapper = new pc.Lightmapper(this.graphicsDevice, this.root, this.scene, this.renderer, this.assets);
    this.once("prerender", this._firstBake, this);
    this.keyboard = options.keyboard || null;
    this.mouse = options.mouse || null;
    this.touch = options.touch || null;
    this.gamepads = options.gamepads || null;
    this.vr = null;
    if (options.vr) {
      this._onVrChange(options.vr);
    }
    this._inTools = false;
    this._skyboxLast = 0;
    this._scriptPrefix = options.scriptPrefix || "";
    this.loader.addHandler("animation", new pc.AnimationHandler);
    this.loader.addHandler("model", new pc.ModelHandler(this.graphicsDevice));
    this.loader.addHandler("material", new pc.MaterialHandler(this));
    this.loader.addHandler("texture", new pc.TextureHandler(this.graphicsDevice, this.assets, this.loader));
    this.loader.addHandler("text", new pc.TextHandler);
    this.loader.addHandler("json", new pc.JsonHandler);
    this.loader.addHandler("audio", new pc.AudioHandler(this._audioManager));
    this.loader.addHandler("script", new pc.ScriptHandler(this));
    this.loader.addHandler("scene", new pc.SceneHandler(this));
    this.loader.addHandler("cubemap", new pc.CubemapHandler(this.graphicsDevice, this.assets, this.loader));
    this.loader.addHandler("html", new pc.HtmlHandler);
    this.loader.addHandler("css", new pc.CssHandler);
    this.loader.addHandler("shader", new pc.ShaderHandler);
    this.loader.addHandler("hierarchy", new pc.HierarchyHandler(this));
    this.loader.addHandler("scenesettings", new pc.SceneSettingsHandler(this));
    this.loader.addHandler("folder", new pc.FolderHandler);
    this.loader.addHandler("font", new pc.FontHandler(this.loader));
    this.loader.addHandler("binary", new pc.BinaryHandler);
    var rigidbodysys = new pc.RigidBodyComponentSystem(this);
    var collisionsys = new pc.CollisionComponentSystem(this);
    var animationsys = new pc.AnimationComponentSystem(this);
    var modelsys = new pc.ModelComponentSystem(this);
    var camerasys = new pc.CameraComponentSystem(this);
    var lightsys = new pc.LightComponentSystem(this);
    if (pc.script.legacy) {
      new pc.ScriptLegacyComponentSystem(this);
    } else {
      new pc.ScriptComponentSystem(this);
    }
    var audiosourcesys = new pc.AudioSourceComponentSystem(this, this._audioManager);
    var soundsys = new pc.SoundComponentSystem(this, this._audioManager);
    var audiolistenersys = new pc.AudioListenerComponentSystem(this, this._audioManager);
    var particlesystemsys = new pc.ParticleSystemComponentSystem(this);
    var screensys = new pc.ScreenComponentSystem(this);
    var elementsys = new pc.ElementComponentSystem(this);
    var zonesys = new pc.ZoneComponentSystem(this);
    this._visibilityChangeHandler = this.onVisibilityChange.bind(this);
    if (document.hidden !== undefined) {
      this._hiddenAttr = "hidden";
      document.addEventListener("visibilitychange", this._visibilityChangeHandler, false);
    } else {
      if (document.mozHidden !== undefined) {
        this._hiddenAttr = "mozHidden";
        document.addEventListener("mozvisibilitychange", this._visibilityChangeHandler, false);
      } else {
        if (document.msHidden !== undefined) {
          this._hiddenAttr = "msHidden";
          document.addEventListener("msvisibilitychange", this._visibilityChangeHandler, false);
        } else {
          if (document.webkitHidden !== undefined) {
            this._hiddenAttr = "webkitHidden";
            document.addEventListener("webkitvisibilitychange", this._visibilityChangeHandler, false);
          }
        }
      }
    }
    this.tick = makeTick(this);
  };
  Application._currentApplication = null;
  Application._applications = {};
  Application.getApplication = function(id) {
    if (id) {
      return Application._applications[id];
    } else {
      return Application._currentApplication;
    }
  };
  var Progress = function(length) {
    this.length = length;
    this.count = 0;
    this.inc = function() {
      this.count++;
    };
    this.done = function() {
      return this.count === this.length;
    };
  };
  Application.prototype = {configure:function(url, callback) {
    var self = this;
    pc.http.get(url, function(err, response) {
      if (err) {
        callback(err);
        return;
      }
      var props = response["application_properties"];
      var assets = response["assets"];
      var scripts = response["scripts"];
      var priorityScripts = response["priority_scripts"];
      self._parseApplicationProperties(props, function(err) {
        self._onVrChange(props.vr);
        self._parseAssets(assets);
        if (!err) {
          callback(null);
        } else {
          callback(err);
        }
      });
    });
  }, preload:function(callback) {
    var self = this;
    self.fire("preload:start");
    var assets = this.assets.list({preload:true});
    var _assets = new Progress(assets.length);
    var _done = false;
    var done = function() {
      if (!self.graphicsDevice) {
        return;
      }
      if (!_done && _assets.done()) {
        _done = true;
        self.fire("preload:end");
        callback();
      }
    };
    var total = assets.length;
    var count = function() {
      return _assets.count;
    };
    var i;
    if (_assets.length) {
      var onAssetLoad = function(asset) {
        _assets.inc();
        self.fire("preload:progress", count() / total);
        if (_assets.done()) {
          done();
        }
      };
      var onAssetError = function(err, asset) {
        _assets.inc();
        self.fire("preload:progress", count() / total);
        if (_assets.done()) {
          done();
        }
      };
      for (i = 0;i < assets.length;i++) {
        if (!assets[i].loaded) {
          assets[i].once("load", onAssetLoad);
          assets[i].once("error", onAssetError);
          this.assets.load(assets[i]);
        } else {
          _assets.inc();
          self.fire("preload:progress", count() / total);
          if (_assets.done()) {
            done();
          }
        }
      }
    } else {
      done();
    }
  }, loadSceneHierarchy:function(url, callback) {
    var self = this;
    var handler = this.loader.getHandler("hierarchy");
    if (this.assets && this.assets.prefix && !pc.ABSOLUTE_URL.test(url)) {
      url = pc.path.join(this.assets.prefix, url);
    }
    handler.load(url, function(err, data) {
      if (err) {
        if (callback) {
          callback(err);
        }
        return;
      }
      var _loaded = function() {
        var entity = handler.open(url, data);
        self.loader.clearCache(url, "hierarchy");
        self.root.addChild(entity);
        pc.ComponentSystem.initialize(entity);
        pc.ComponentSystem.postInitialize(entity);
        if (callback) {
          callback(err, entity);
        }
      };
      self._preloadScripts(data, _loaded);
    });
  }, loadSceneSettings:function(url, callback) {
    if (this.assets && this.assets.prefix && !pc.ABSOLUTE_URL.test(url)) {
      url = pc.path.join(this.assets.prefix, url);
    }
    this.loader.load(url, "scenesettings", function(err, settings) {
      if (!err) {
        this.applySceneSettings(settings);
        if (callback) {
          callback(null);
        }
      } else {
        if (callback) {
          callback(err);
        }
      }
    }.bind(this));
  }, loadScene:function(url, callback) {
    var self = this;
    var handler = this.loader.getHandler("scene");
    if (this.assets && this.assets.prefix && !pc.ABSOLUTE_URL.test(url)) {
      url = pc.path.join(this.assets.prefix, url);
    }
    handler.load(url, function(err, data) {
      if (!err) {
        var _loaded = function() {
          var scene = handler.open(url, data);
          self.loader.clearCache(url, "scene");
          self.loader.patch({resource:scene, type:"scene"}, self.assets);
          self.root.addChild(scene.root);
          if (self.systems.rigidbody && typeof Ammo !== "undefined") {
            self.systems.rigidbody.setGravity(scene._gravity.x, scene._gravity.y, scene._gravity.z);
          }
          if (callback) {
            callback(null, scene);
          }
        };
        this._preloadScripts(data, _loaded);
      } else {
        if (callback) {
          callback(err);
        }
      }
    }.bind(this));
  }, _preloadScripts:function(sceneData, callback) {
    if (!pc.script.legacy) {
      callback();
      return;
    }
    var self = this;
    self.systems.script.preloading = true;
    var scripts = this._getScriptReferences(sceneData);
    var i = 0, l = scripts.length;
    var progress = new Progress(l);
    var scriptUrl;
    var regex = /^http(s)?:\/\//;
    if (l) {
      var onLoad = function(err, ScriptType) {
        if (err) {
          console.error(err);
        }
        progress.inc();
        if (progress.done()) {
          self.systems.script.preloading = false;
          callback();
        }
      };
      for (i = 0;i < l;i++) {
        scriptUrl = scripts[i];
        if (!regex.test(scriptUrl.toLowerCase()) && self._scriptPrefix) {
          scriptUrl = pc.path.join(self._scriptPrefix, scripts[i]);
        }
        this.loader.load(scriptUrl, "script", onLoad);
      }
    } else {
      self.systems.script.preloading = false;
      callback();
    }
  }, _parseApplicationProperties:function(props, callback) {
    this._width = props.width;
    this._height = props.height;
    if (props.use_device_pixel_ratio) {
      this.graphicsDevice.maxPixelRatio = window.devicePixelRatio;
    }
    this.setCanvasResolution(props.resolution_mode, this._width, this._height);
    this.setCanvasFillMode(props.fill_mode, this._width, this._height);
    if (props.vr && props.vr_polyfill_url) {
      if (!pc.VrManager.isSupported) {
        props.libraries.push(props.vr_polyfill_url);
      }
    }
    this._loadLibraries(props.libraries, callback);
  }, _loadLibraries:function(urls, callback) {
    var len = urls.length;
    var count = len;
    var self = this;
    var regex = /^http(s)?:\/\//;
    if (len) {
      var onLoad = function(err, script) {
        count--;
        if (err) {
          callback(err);
        } else {
          if (count === 0) {
            self.onLibrariesLoaded();
            callback(null);
          }
        }
      };
      for (var i = 0;i < len;++i) {
        var url = urls[i];
        if (!regex.test(url.toLowerCase()) && self._scriptPrefix) {
          url = pc.path.join(self._scriptPrefix, url);
        }
        this.loader.load(url, "script", onLoad);
      }
    } else {
      callback(null);
    }
  }, _parseAssets:function(assets) {
    var scripts = [];
    var list = [];
    var scriptsIndex = {};
    if (!pc.script.legacy) {
      for (var i = 0;i < this.scriptsOrder.length;i++) {
        var id = this.scriptsOrder[i];
        if (!assets[id]) {
          continue;
        }
        scriptsIndex[id] = true;
        list.push(assets[id]);
      }
      for (var id in assets) {
        if (scriptsIndex[id]) {
          continue;
        }
        list.push(assets[id]);
      }
    } else {
      for (var id in assets) {
        list.push(assets[id]);
      }
    }
    for (var i = 0;i < list.length;i++) {
      var data = list[i];
      var asset = new pc.Asset(data.name, data.type, data.file, data.data);
      asset.id = parseInt(data.id);
      asset.preload = data.preload ? data.preload : false;
      asset.tags.add(data.tags);
      this.assets.add(asset);
    }
  }, _getScriptReferences:function(scene) {
    var i, key;
    var priorityScripts = [];
    if (scene.settings.priority_scripts) {
      priorityScripts = scene.settings.priority_scripts;
    }
    var _scripts = [];
    var _index = {};
    for (i = 0;i < priorityScripts.length;i++) {
      _scripts.push(priorityScripts[i]);
      _index[priorityScripts[i]] = true;
    }
    var entities = scene.entities;
    for (key in entities) {
      if (!entities[key].components.script) {
        continue;
      }
      var scripts = entities[key].components.script.scripts;
      for (i = 0;i < scripts.length;i++) {
        if (_index[scripts[i].url]) {
          continue;
        }
        _scripts.push(scripts[i].url);
        _index[scripts[i].url] = true;
      }
    }
    return _scripts;
  }, start:function() {
    this.fire("start", {timestamp:pc.now(), target:this});
    if (!this._librariesLoaded) {
      this.onLibrariesLoaded();
    }
    pc.ComponentSystem.initialize(this.root);
    this.fire("initialize");
    pc.ComponentSystem.postInitialize(this.root);
    this.fire("postinitialize");
    this.tick();
  }, update:function(dt) {
    this.graphicsDevice.updateClientRect();
    if (this.vr) {
      this.vr.poll();
    }
    if (pc.script.legacy) {
      pc.ComponentSystem.fixedUpdate(1 / 60, this._inTools);
    }
    pc.ComponentSystem.update(dt, this._inTools);
    pc.ComponentSystem.postUpdate(dt, this._inTools);
    this.fire("update", dt);
    if (this.controller) {
      this.controller.update(dt);
    }
    if (this.mouse) {
      this.mouse.update(dt);
    }
    if (this.keyboard) {
      this.keyboard.update(dt);
    }
    if (this.gamepads) {
      this.gamepads.update(dt);
    }
  }, render:function() {
    this.fire("prerender");
    var cameras = this.systems.camera.cameras;
    var camera = null;
    var renderer = this.renderer;
    this.root.syncHierarchy();
    for (var i = 0, len = cameras.length;i < len;i++) {
      camera = cameras[i];
      camera.frameBegin();
      renderer.render(this.scene, camera.camera);
      camera.frameEnd();
    }
  }, _fillFrameStats:function(now, dt, ms) {
    var stats = this.stats.frame;
    stats.dt = dt;
    stats.ms = ms;
    if (now > stats._timeToCountFrames) {
      stats.fps = stats._fpsAccum;
      stats._fpsAccum = 0;
      stats._timeToCountFrames = now + 1E3;
    } else {
      stats._fpsAccum++;
    }
    stats.cameras = this.renderer._camerasRendered;
    stats.materials = this.renderer._materialSwitches;
    stats.shaders = this.graphicsDevice._shaderSwitchesPerFrame;
    stats.shadowMapUpdates = this.renderer._shadowMapUpdates;
    stats.shadowMapTime = this.renderer._shadowMapTime;
    stats.depthMapTime = this.renderer._depthMapTime;
    stats.forwardTime = this.renderer._forwardTime;
    var prims = this.graphicsDevice._primsPerFrame;
    stats.triangles = prims[pc.PRIMITIVE_TRIANGLES] / 3 + Math.max(prims[pc.PRIMITIVE_TRISTRIP] - 2, 0) + Math.max(prims[pc.PRIMITIVE_TRIFAN] - 2, 0);
    stats.cullTime = this.renderer._cullTime;
    stats.sortTime = this.renderer._sortTime;
    stats.skinTime = this.renderer._skinTime;
    stats.instancingTime = this.renderer._instancingTime;
    stats.otherPrimitives = 0;
    for (var i = 0;i < prims.length;i++) {
      if (i < pc.PRIMITIVE_TRIANGLES) {
        stats.otherPrimitives += prims[i];
      }
      prims[i] = 0;
    }
    this.renderer._camerasRendered = 0;
    this.renderer._materialSwitches = 0;
    this.renderer._shadowMapUpdates = 0;
    this.graphicsDevice._shaderSwitchesPerFrame = 0;
    this.renderer._cullTime = 0;
    this.renderer._sortTime = 0;
    this.renderer._skinTime = 0;
    this.renderer._instancingTime = 0;
    stats = this.stats.drawCalls;
    stats.forward = this.renderer._forwardDrawCalls;
    stats.depth = this.renderer._depthDrawCalls;
    stats.shadow = this.renderer._shadowDrawCalls;
    stats.skinned = this.renderer._skinDrawCalls;
    stats.immediate = this.renderer._immediateRendered;
    stats.instanced = this.renderer._instancedDrawCalls;
    stats.removedByInstancing = this.renderer._removedByInstancing;
    stats.total = this.graphicsDevice._drawCallsPerFrame;
    stats.misc = stats.total - (stats.forward + stats.depth + stats.shadow);
    this.renderer._depthDrawCalls = 0;
    this.renderer._shadowDrawCalls = 0;
    this.renderer._forwardDrawCalls = 0;
    this.renderer._skinDrawCalls = 0;
    this.renderer._immediateRendered = 0;
    this.renderer._instancedDrawCalls = 0;
    this.renderer._removedByInstancing = 0;
    this.graphicsDevice._drawCallsPerFrame = 0;
    this.stats.misc.renderTargetCreationTime = this.graphicsDevice.renderTargetCreationTime;
    stats = this.stats.particles;
    stats.updatesPerFrame = stats._updatesPerFrame;
    stats.frameTime = stats._frameTime;
    stats._updatesPerFrame = 0;
    stats._frameTime = 0;
  }, setCanvasFillMode:function(mode, width, height) {
    this._fillMode = mode;
    this.resizeCanvas(width, height);
  }, setCanvasResolution:function(mode, width, height) {
    this._resolutionMode = mode;
    if (mode === pc.RESOLUTION_AUTO && width === undefined) {
      width = this.graphicsDevice.canvas.clientWidth;
      height = this.graphicsDevice.canvas.clientHeight;
    }
    this.graphicsDevice.resizeCanvas(width, height);
  }, isFullscreen:function() {
    return !!document.fullscreenElement;
  }, enableFullscreen:function(element, success, error) {
    element = element || this.graphicsDevice.canvas;
    var s = function() {
      success();
      document.removeEventListener("fullscreenchange", s);
    };
    var e = function() {
      error();
      document.removeEventListener("fullscreenerror", e);
    };
    if (success) {
      document.addEventListener("fullscreenchange", s, false);
    }
    if (error) {
      document.addEventListener("fullscreenerror", e, false);
    }
    if (element.requestFullscreen) {
      element.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
    } else {
      error();
    }
  }, disableFullscreen:function(success) {
    var s = function() {
      success();
      document.removeEventListener("fullscreenchange", s);
    };
    if (success) {
      document.addEventListener("fullscreenchange", s, false);
    }
    document.exitFullscreen();
  }, isHidden:function() {
    return document[this._hiddenAttr];
  }, onVisibilityChange:function(e) {
    if (this.isHidden()) {
      this._audioManager.suspend();
    } else {
      this._audioManager.resume();
    }
  }, resizeCanvas:function(width, height) {
    if (!this._allowResize) {
      return;
    }
    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;
    if (navigator.isCocoonJS) {
      width = windowWidth;
      height = windowHeight;
      this.graphicsDevice.resizeCanvas(width, height);
    } else {
      if (this._fillMode === pc.FILLMODE_KEEP_ASPECT) {
        var r = this.graphicsDevice.canvas.width / this.graphicsDevice.canvas.height;
        var winR = windowWidth / windowHeight;
        if (r > winR) {
          width = windowWidth;
          height = width / r;
        } else {
          height = windowHeight;
          width = height * r;
        }
      } else {
        if (this._fillMode === pc.FILLMODE_FILL_WINDOW) {
          width = windowWidth;
          height = windowHeight;
        } else {
        }
      }
      this.graphicsDevice.canvas.style.width = width + "px";
      this.graphicsDevice.canvas.style.height = height + "px";
      if (this._resolutionMode === pc.RESOLUTION_AUTO) {
        this.setCanvasResolution(pc.RESOLUTION_AUTO);
      }
    }
    return {width:width, height:height};
  }, onLibrariesLoaded:function() {
    this._librariesLoaded = true;
    this.systems.rigidbody.onLibraryLoaded();
    this.systems.collision.onLibraryLoaded();
  }, applySceneSettings:function(settings) {
    var asset;
    if (this.systems.rigidbody && typeof Ammo !== "undefined") {
      var gravity = settings.physics.gravity;
      this.systems.rigidbody.setGravity(gravity[0], gravity[1], gravity[2]);
    }
    this.scene.applySettings(settings);
    if (settings.render.hasOwnProperty("skybox")) {
      if (settings.render.skybox) {
        var asset = this.assets.get(settings.render.skybox);
        if (asset) {
          this.setSkybox(asset);
        } else {
          this.assets.once("add:" + settings.render.skybox, this.setSkybox, this);
        }
      } else {
        this.setSkybox(null);
      }
    }
  }, setSkybox:function(asset) {
    if (asset) {
      if (this._skyboxLast === asset.id) {
        if (this.scene.skyboxMip === 0 && !asset.loadFaces) {
          this._skyboxLoad(asset);
        } else {
          this._onSkyboxChange(asset);
        }
        return;
      }
      if (this._skyboxLast) {
        this.assets.off("add:" + this._skyboxLast, this.setSkybox, this);
        this.assets.off("load:" + this._skyboxLast, this._onSkyboxChange, this);
        this.assets.off("remove:" + this._skyboxLast, this._skyboxRemove, this);
      }
      this._skyboxLast = asset.id;
      this.assets.on("load:" + asset.id, this._onSkyboxChange, this);
      this.assets.once("remove:" + asset.id, this._skyboxRemove, this);
      if (asset.resource) {
        this.scene.setSkybox(asset.resources);
      }
      this._skyboxLoad(asset);
    } else {
      if (!this._skyboxLast) {
        return;
      }
      this._skyboxRemove({id:this._skyboxLast});
    }
  }, _onVrChange:function(enabled) {
    if (enabled) {
      if (!this.vr) {
        this.vr = new pc.VrManager(this);
      }
    } else {
      if (this.vr) {
        this.vr.destroy();
        this.vr = null;
      }
    }
  }, _onSkyboxChange:function(asset) {
    this.scene.setSkybox(asset.resources);
  }, _skyboxLoad:function(asset) {
    if (this.scene.skyboxMip === 0) {
      asset.loadFaces = true;
    }
    this.assets.load(asset);
    this._onSkyboxChange(asset);
  }, _skyboxRemove:function(asset) {
    if (!this._skyboxLast) {
      return;
    }
    this.assets.off("add:" + asset.id, this.setSkybox, this);
    this.assets.off("load:" + asset.id, this._onSkyboxChange, this);
    this.assets.off("remove:" + asset.id, this._skyboxRemove, this);
    this.scene.setSkybox(null);
    this._skyboxLast = null;
  }, _firstBake:function() {
    this.lightmapper.bake(null, this.scene.lightmapMode);
  }, destroy:function() {
    Application._applications[this.graphicsDevice.canvas.id] = null;
    this.off("librariesloaded");
    document.removeEventListener("visibilitychange", this._visibilityChangeHandler);
    document.removeEventListener("mozvisibilitychange", this._visibilityChangeHandler);
    document.removeEventListener("msvisibilitychange", this._visibilityChangeHandler);
    document.removeEventListener("webkitvisibilitychange", this._visibilityChangeHandler);
    if (this.mouse) {
      this.mouse.off("mouseup");
      this.mouse.off("mousedown");
      this.mouse.off("mousewheel");
      this.mouse.off("mousemove");
      this.mouse = null;
    }
    if (this.keyboard) {
      this.keyboard.off("keydown");
      this.keyboard.off("keyup");
      this.keyboard.off("keypress");
      this.keyboard = null;
    }
    if (this.touch) {
      this.touch.off("touchstart");
      this.touch.off("touchend");
      this.touch.off("touchmove");
      this.touch.off("touchcancel");
      this.touch = null;
    }
    if (this.controller) {
      this.controller = null;
    }
    this.root.destroy();
    pc.ComponentSystem.destroy();
    this.loader.destroy();
    this.loader = null;
    var assets = this.assets.list();
    for (var i = 0;i < assets.length;i++) {
      assets[i].unload();
    }
    this.scene = null;
    this.systems = [];
    this.context = null;
    this.graphicsDevice.clearShaderCache();
    this.graphicsDevice.destroyed = true;
    this.graphicsDevice = null;
    this.renderer = null;
    if (this._audioManager) {
      this._audioManager.destroy();
      this._audioManager = null;
    }
    pc.http = new pc.Http;
    pc.ParticleEmitter.DEFAULT_PARAM_TEXTURE = null;
    pc.destroyPostEffectQuad();
  }};
  var makeTick = function(_app) {
    var app = _app;
    return function() {
      if (!app.graphicsDevice) {
        return;
      }
      Application._currentApplication = app;
      pc.app = app;
      if (app.vr && app.vr.display && app.vr.display.presenting) {
        app.vr.display.requestAnimationFrame(app.tick);
      } else {
        window.requestAnimationFrame(app.tick);
      }
      var now = pc.now();
      var ms = now - (app._time || now);
      var dt = ms / 1E3;
      app._time = now;
      dt = pc.math.clamp(dt, 0, .1);
      dt *= app.timeScale;
      app.update(dt);
      app.render();
      _frameEndData.timestamp = pc.now();
      _frameEndData.target = app;
      app.fire("frameend", _frameEndData);
      app.fire("frameEnd", _frameEndData);
      if (app.vr && app.vr.display && app.vr.display.presenting) {
        app.vr.display.submitFrame();
      }
    };
  };
  var _frameEndData = {};
  return {FILLMODE_NONE:"NONE", FILLMODE_FILL_WINDOW:"FILL_WINDOW", FILLMODE_KEEP_ASPECT:"KEEP_ASPECT", RESOLUTION_AUTO:"AUTO", RESOLUTION_FIXED:"FIXED", Application:Application};
}());
pc.ApplicationStats = function(device) {
  this.frame = {fps:0, ms:0, dt:0, updateStart:0, updateTime:0, renderStart:0, renderTime:0, physicsStart:0, physicsTime:0, cullTime:0, sortTime:0, skinTime:0, instancingTime:0, triangles:0, otherPrimitives:0, shaders:0, materials:0, cameras:0, shadowMapUpdates:0, shadowMapTime:0, depthMapTime:0, forwardTime:0, _timeToCountFrames:0, _fpsAccum:0};
  this.drawCalls = {forward:0, depth:0, shadow:0, immediate:0, misc:0, total:0, skinned:0, instanced:0, removedByInstancing:0};
  this.misc = {renderTargetCreationTime:0};
  this.particles = {updatesPerFrame:0, _updatesPerFrame:0, frameTime:0, _frameTime:0};
  this.vram = device._vram;
  this.shaders = device._shaderStats;
  Object.defineProperty(this.vram, "totalUsed", {get:function() {
    return this.tex + this.vb + this.ib;
  }});
  Object.defineProperty(this, "scene", {get:function() {
    return pc.Application._currentApplication.scene._stats;
  }});
  Object.defineProperty(this, "lightmapper", {get:function() {
    return pc.Application._currentApplication.lightmapper._stats;
  }});
  pc.events.attach(this);
};
pc.extend(pc, function() {
  var ComponentSystemRegistry = function() {
  };
  ComponentSystemRegistry.prototype = {add:function(name, system) {
    if (!this[name]) {
      this[name] = system;
      system.name = name;
    } else {
      throw new Error(pc.string.format("ComponentSystem name '{0}' already registered or not allowed", name));
    }
  }, remove:function(name) {
    if (!this[name]) {
      throw new Error(pc.string.format("No ComponentSystem named '{0}' registered", name));
    }
    delete this[name];
  }, list:function() {
    var list = Object.keys(this);
    var defaultPriority = 1;
    var priorities = {"collisionrect":.5, "collisioncircle":.5};
    list.sort(function(a, b) {
      var pa = priorities[a] || defaultPriority;
      var pb = priorities[b] || defaultPriority;
      if (pa < pb) {
        return -1;
      } else {
        if (pa > pb) {
          return 1;
        }
      }
      return 0;
    });
    return list.map(function(key) {
      return this[key];
    }, this);
  }, getComponentSystemOrder:function() {
    var index;
    var names = Object.keys(this);
    index = names.indexOf("collisionrect");
    names.splice(index, 1);
    names.unshift("collisionrect");
    index = names.indexOf("collisioncircle");
    names.splice(index, 1);
    names.unshift("collisioncircle");
    return names;
  }};
  return {ComponentSystemRegistry:ComponentSystemRegistry};
}());
pc.extend(pc, function() {
  var ComponentSystem = function(app) {
    this.app = app;
    this.dataStore = {};
    this.schema = [];
    pc.events.attach(this);
  };
  pc.extend(ComponentSystem, {initialize:function(root) {
    ComponentSystem.fire("initialize", root);
  }, postInitialize:function(root) {
    ComponentSystem.fire("postInitialize", root);
  }, update:function(dt, inTools) {
    if (inTools) {
      ComponentSystem.fire("toolsUpdate", dt);
    } else {
      ComponentSystem.fire("update", dt);
    }
  }, fixedUpdate:function(dt, inTools) {
    ComponentSystem.fire("fixedUpdate", dt);
  }, postUpdate:function(dt, inTools) {
    ComponentSystem.fire("postUpdate", dt);
  }});
  ComponentSystem.prototype = {get store() {
    return this.dataStore;
  }, addComponent:function(entity, data) {
    var component = new this.ComponentType(this, entity);
    var componentData = new this.DataType;
    data = data || {};
    this.dataStore[entity._guid] = {entity:entity, data:componentData};
    entity[this.id] = component;
    entity.c[this.id] = component;
    this.initializeComponentData(component, data, []);
    this.fire("add", entity, component);
    return component;
  }, removeComponent:function(entity) {
    var record = this.dataStore[entity._guid];
    var component = entity.c[this.id];
    this.fire("beforeremove", entity, component);
    delete this.dataStore[entity._guid];
    delete entity[this.id];
    delete entity.c[this.id];
    this.fire("remove", entity, record.data);
  }, cloneComponent:function(entity, clone) {
    var src = this.dataStore[entity._guid];
    return this.addComponent(clone, src.data);
  }, initializeComponentData:function(component, data, properties) {
    data = data || {};
    properties.forEach(function(value) {
      if (data[value] !== undefined) {
        component[value] = data[value];
      } else {
        component[value] = component.data[value];
      }
    }, this);
    if (component.enabled && component.entity.enabled) {
      component.onEnable();
    }
  }};
  pc.events.attach(ComponentSystem);
  ComponentSystem.destroy = function() {
    ComponentSystem.off("initialize");
    ComponentSystem.off("postInitialize");
    ComponentSystem.off("toolsUpdate");
    ComponentSystem.off("update");
    ComponentSystem.off("fixedUpdate");
    ComponentSystem.off("postUpdate");
  };
  return {ComponentSystem:ComponentSystem};
}());
pc.extend(pc, function() {
  var Component = function(system, entity) {
    this.system = system;
    this.entity = entity;
    pc.events.attach(this);
    if (this.system.schema) {
      this.buildAccessors(this.system.schema);
    }
    this.on("set", function(name, oldValue, newValue) {
      this.fire("set_" + name, name, oldValue, newValue);
    });
    this.on("set_enabled", this.onSetEnabled, this);
  };
  Component.prototype = {get data() {
    var record = this.system.store[this.entity._guid];
    if (record) {
      return record.data;
    } else {
      return null;
    }
  }, buildAccessors:function(schema) {
    var self = this;
    schema.forEach(function(prop) {
      Object.defineProperty(self, prop, {get:function() {
        return self.data[prop];
      }, set:function(value) {
        var data = self.data;
        var oldValue = data[prop];
        data[prop] = value;
        self.fire("set", prop, oldValue, value);
      }, configurable:true});
    });
  }, onSetEnabled:function(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      if (this.entity.enabled) {
        if (newValue) {
          this.onEnable();
        } else {
          this.onDisable();
        }
      }
    }
  }, onEnable:function() {
  }, onDisable:function() {
  }, onPostStateChange:function() {
  }};
  return {Component:Component};
}());
pc.extend(pc, function() {
  var ComponentData = function() {
  };
  return {ComponentData:ComponentData};
}());
pc.extend(pc, function() {
  var AnimationComponent = function(system, entity) {
    this.animationsIndex = {};
    this.on("set_animations", this.onSetAnimations, this);
    this.on("set_assets", this.onSetAssets, this);
    this.on("set_loop", this.onSetLoop, this);
  };
  AnimationComponent = pc.inherits(AnimationComponent, pc.Component);
  pc.extend(AnimationComponent.prototype, {play:function(name, blendTime) {
    if (!this.data.animations[name]) {
      console.error(pc.string.format("Trying to play animation '{0}' which doesn't exist", name));
      return;
    }
    if (!this.enabled || !this.entity.enabled) {
      return;
    }
    blendTime = blendTime || 0;
    var data = this.data;
    data.prevAnim = data.currAnim;
    data.currAnim = name;
    if (data.model) {
      data.blending = blendTime > 0 && data.prevAnim;
      if (data.blending) {
        data.blendTime = blendTime;
        data.blendTimeRemaining = blendTime;
        data.fromSkel.animation = data.animations[data.prevAnim];
        data.fromSkel.addTime(data.skeleton._time);
        data.toSkel.animation = data.animations[data.currAnim];
      } else {
        data.skeleton.animation = data.animations[data.currAnim];
      }
    }
    data.playing = true;
  }, getAnimation:function(name) {
    return this.data.animations[name];
  }, setModel:function(model) {
    var data = this.data;
    if (model) {
      var graph = model.getGraph();
      data.fromSkel = new pc.Skeleton(graph);
      data.toSkel = new pc.Skeleton(graph);
      data.skeleton = new pc.Skeleton(graph);
      data.skeleton.looping = data.loop;
      data.skeleton.setGraph(graph);
    }
    data.model = model;
    if (data.animations && data.currAnim && data.animations[data.currAnim]) {
      this.play(data.currAnim);
    }
  }, loadAnimationAssets:function(ids) {
    if (!ids || !ids.length) {
      return;
    }
    var self = this;
    var assets = this.system.app.assets;
    var i, l = ids.length;
    var onAssetReady = function(asset) {
      self.animations[asset.name] = asset.resource;
      self.animationsIndex[asset.id] = asset.name;
      self.animations = self.animations;
    };
    var onAssetAdd = function(asset) {
      asset.off("change", self.onAssetChanged, self);
      asset.on("change", self.onAssetChanged, self);
      asset.off("remove", self.onAssetRemoved, self);
      asset.on("remove", self.onAssetRemoved, self);
      if (asset.resource) {
        onAssetReady(asset);
      } else {
        asset.once("load", onAssetReady, self);
        if (self.enabled && self.entity.enabled) {
          assets.load(asset);
        }
      }
    };
    for (i = 0;i < l;i++) {
      var asset = assets.get(ids[i]);
      if (asset) {
        onAssetAdd(asset);
      } else {
        assets.on("add:" + ids[i], onAssetAdd);
      }
    }
  }, onAssetChanged:function(asset, attribute, newValue, oldValue) {
    if (attribute === "resource") {
      if (newValue) {
        this.animations[asset.name] = newValue;
        this.animationsIndex[asset.id] = asset.name;
        if (this.data.currAnim === asset.name) {
          if (this.data.playing && this.data.enabled && this.entity.enabled) {
            this.play(asset.name, 0);
          }
        }
      } else {
        delete this.animations[asset.name];
        delete this.animationsIndex[asset.id];
      }
    }
  }, onAssetRemoved:function(asset) {
    asset.off("remove", this.onAssetRemoved, this);
    if (this.animations && this.animations[asset.name]) {
      delete this.animations[asset.name];
      delete this.animationsIndex[asset.id];
      if (this.data.currAnim === asset.name) {
        this._stopCurrentAnimation();
      }
    }
  }, _stopCurrentAnimation:function() {
    this.data.currAnim = null;
    this.data.playing = false;
    if (this.data.skeleton) {
      this.data.skeleton.currentTime = 0;
      this.data.skeleton.animation = null;
    }
  }, onSetAnimations:function(name, oldValue, newValue) {
    var data = this.data;
    var modelComponent = this.entity.model;
    if (modelComponent) {
      var m = modelComponent.model;
      if (m && m !== data.model) {
        this.entity.animation.setModel(m);
      }
    }
    if (!data.currAnim && data.activate && data.enabled && this.entity.enabled) {
      for (var animName in data.animations) {
        this.play(animName, 0);
        break;
      }
    }
  }, onSetAssets:function(name, oldValue, newValue) {
    if (oldValue && oldValue.length) {
      for (var i = 0;i < oldValue.length;i++) {
        if (oldValue[i]) {
          var asset = this.system.app.assets.get(oldValue[i]);
          if (asset) {
            asset.off("change", this.onAssetChanged, this);
            asset.off("remove", this.onAssetRemoved, this);
            var name = this.animationsIndex[asset.id];
            if (this.data.currAnim === name) {
              this._stopCurrentAnimation();
            }
            delete this.animations[name];
            delete this.animationsIndex[asset.id];
          }
        }
      }
    }
    var ids = newValue.map(function(value) {
      if (value instanceof pc.Asset) {
        return value.id;
      } else {
        return value;
      }
    });
    this.loadAnimationAssets(ids);
  }, onSetLoop:function(name, oldValue, newValue) {
    if (this.data.skeleton) {
      this.data.skeleton.looping = this.data.loop;
    }
  }, onSetCurrentTime:function(name, oldValue, newValue) {
    this.data.skeleton.currentTime = newValue;
    this.data.skeleton.addTime(0);
    this.data.skeleton.updateGraph();
  }, onEnable:function() {
    AnimationComponent._super.onEnable.call(this);
    var assets = this.data.assets;
    var registry = this.system.app.assets;
    if (assets) {
      for (var i = 0, len = assets.length;i < len;i++) {
        var asset = assets[i];
        if (!(asset instanceof pc.Asset)) {
          asset = registry.get(asset);
        }
        if (asset && !asset.resource) {
          registry.load(asset);
        }
      }
    }
    if (this.data.activate && !this.data.currAnim) {
      for (var animName in this.data.animations) {
        this.play(animName, 0);
        break;
      }
    }
  }, onBeforeRemove:function() {
    for (var i = 0;i < this.assets.length;i++) {
      var asset = this.system.app.assets.get(this.assets[i]);
      if (!asset) {
        continue;
      }
      asset.off("change", this.onAssetChanged, this);
      asset.off("remove", this.onAssetRemoved, this);
    }
    delete this.data.animation;
    delete this.data.skeleton;
    delete this.data.fromSkel;
    delete this.data.toSkel;
  }});
  Object.defineProperties(AnimationComponent.prototype, {currentTime:{get:function() {
    return this.data.skeleton._time;
  }, set:function(currentTime) {
    this.data.skeleton.currentTime = currentTime;
    this.data.skeleton.addTime(0);
    this.data.skeleton.updateGraph();
  }}, duration:{get:function() {
    return this.data.animations[this.data.currAnim].duration;
  }}});
  return {AnimationComponent:AnimationComponent};
}());
pc.extend(pc, function() {
  var AnimationComponentSystem = function AnimationComponentSystem(app) {
    this.id = "animation";
    this.description = "Specifies the animation assets that can run on the model specified by the Entity's model Component.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.AnimationComponent;
    this.DataType = pc.AnimationComponentData;
    this.schema = ["enabled", "assets", "speed", "loop", "activate", "animations", "skeleton", "model", "prevAnim", "currAnim", "fromSkel", "toSkel", "blending", "blendTimeRemaining", "playing"];
    this.on("beforeremove", this.onBeforeRemove, this);
    this.on("update", this.onUpdate, this);
    pc.ComponentSystem.on("update", this.onUpdate, this);
  };
  AnimationComponentSystem = pc.inherits(AnimationComponentSystem, pc.ComponentSystem);
  pc.extend(AnimationComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    properties = ["activate", "enabled", "loop", "speed", "assets"];
    AnimationComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, cloneComponent:function(entity, clone) {
    var component = this.addComponent(clone, {});
    clone.animation.data.assets = pc.extend([], entity.animation.assets);
    clone.animation.data.speed = entity.animation.speed;
    clone.animation.data.loop = entity.animation.loop;
    clone.animation.data.activate = entity.animation.activate;
    clone.animation.data.enabled = entity.animation.enabled;
    var clonedAnimations = {};
    var animations = entity.animation.animations;
    for (var key in animations) {
      if (animations.hasOwnProperty(key)) {
        clonedAnimations[key] = animations[key];
      }
    }
    clone.animation.animations = clonedAnimations;
    var clonedAnimationsIndex = {};
    var animationsIndex = entity.animation.animationsIndex;
    for (var key in animationsIndex) {
      if (animationsIndex.hasOwnProperty(key)) {
        clonedAnimationsIndex[key] = animationsIndex[key];
      }
    }
    clone.animation.animationsIndex = clonedAnimationsIndex;
  }, onBeforeRemove:function(entity, component) {
    component.onBeforeRemove();
  }, onUpdate:function(dt) {
    var components = this.store;
    for (var id in components) {
      if (components.hasOwnProperty(id)) {
        var component = components[id];
        var componentData = component.data;
        if (componentData.enabled && componentData.playing && component.entity.enabled) {
          var skeleton = componentData.skeleton;
          if (skeleton !== null && componentData.model !== null) {
            if (componentData.blending) {
              componentData.blendTimeRemaining -= dt;
              if (componentData.blendTimeRemaining < 0) {
                componentData.blendTimeRemaining = 0;
              }
              var alpha = 1 - componentData.blendTimeRemaining / componentData.blendTime;
              skeleton.blend(componentData.fromSkel, componentData.toSkel, alpha);
            } else {
              var delta = dt * componentData.speed;
              skeleton.addTime(delta);
              if (skeleton._time === skeleton._animation.duration && !componentData.loop) {
                componentData.playing = false;
              }
            }
            if (componentData.blending && componentData.blendTimeRemaining === 0) {
              componentData.blending = false;
              skeleton.animation = componentData.toSkel._animation;
            }
            skeleton.updateGraph();
          }
        }
      }
    }
  }});
  return {AnimationComponentSystem:AnimationComponentSystem};
}());
pc.extend(pc, function() {
  var AnimationComponentData = function() {
    this.assets = [];
    this.speed = 1;
    this.loop = true;
    this.activate = true;
    this.enabled = true;
    this.animations = {};
    this.skeleton = null;
    this.model = null;
    this.prevAnim = null;
    this.currAnim = null;
    this.fromSkel = null;
    this.toSkel = null;
    this.blending = false;
    this.blendTime = 0;
    this.blendTimeRemaining = 0;
    this.playing = false;
  };
  AnimationComponentData = pc.inherits(AnimationComponentData, pc.ComponentData);
  return {AnimationComponentData:AnimationComponentData};
}());
pc.extend(pc, function() {
  var ModelComponentSystem = function ModelComponentSystem(app) {
    this.id = "model";
    this.description = "Renders a 3D model at the location of the Entity.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.ModelComponent;
    this.DataType = pc.ModelComponentData;
    this.schema = ["enabled", "type", "asset", "materialAsset", "castShadows", "receiveShadows", "castShadowsLightmap", "lightmapped", "lightmapSizeMultiplier", "isStatic", "material", "model", "mapping"];
    var gd = app.graphicsDevice;
    this.box = pc.createBox(gd, {halfExtents:new pc.Vec3(.5, .5, .5)});
    this.capsule = pc.createCapsule(gd, {radius:.5, height:2});
    this.sphere = pc.createSphere(gd, {radius:.5});
    this.cone = pc.createCone(gd, {baseRadius:.5, peakRadius:0, height:1});
    this.cylinder = pc.createCylinder(gd, {radius:.5, height:1});
    this.plane = pc.createPlane(gd, {halfExtents:new pc.Vec2(.5, .5), widthSegments:1, lengthSegments:1});
    this.defaultMaterial = new pc.StandardMaterial;
    this.on("beforeremove", this.onRemove, this);
  };
  ModelComponentSystem = pc.inherits(ModelComponentSystem, pc.ComponentSystem);
  pc.extend(ModelComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    data.material = this.defaultMaterial;
    properties = ["enabled", "material", "materialAsset", "asset", "castShadows", "receiveShadows", "castShadowsLightmap", "lightmapped", "lightmapSizeMultiplier", "type", "mapping", "isStatic"];
    ModelComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, removeComponent:function(entity) {
    var data = entity.model.data;
    entity.model.asset = null;
    if (data.type !== "asset" && data.model) {
      this.app.scene.removeModel(data.model);
      entity.removeChild(data.model.getGraph());
      data.model = null;
    }
    ModelComponentSystem._super.removeComponent.call(this, entity);
  }, cloneComponent:function(entity, clone) {
    var data = {type:entity.model.type, asset:entity.model.asset, castShadows:entity.model.castShadows, receiveShadows:entity.model.receiveShadows, castShadowsLightmap:entity.model.castShadowsLightmap, lightmapped:entity.model.lightmapped, lightmapSizeMultiplier:entity.model.lightmapSizeMultiplier, isStatic:entity.model.isStatic, enabled:entity.model.enabled, mapping:pc.extend({}, entity.model.mapping)};
    var materialAsset = entity.model.materialAsset;
    if (!(materialAsset instanceof pc.Asset) && materialAsset != null) {
      materialAsset = this.app.assets.get(materialAsset);
    }
    var material = entity.model.material;
    if (!material || material === pc.ModelHandler.DEFAULT_MATERIAL || !materialAsset || material === materialAsset.resource) {
      data.materialAsset = materialAsset;
    }
    var component = this.addComponent(clone, data);
    if (!data.materialAsset) {
      component.material = material;
    }
    if (entity.model.model) {
      var meshInstances = entity.model.model.meshInstances;
      var meshInstancesClone = component.model.meshInstances;
      for (var i = 0;i < meshInstances.length;i++) {
        meshInstancesClone[i].mask = meshInstances[i].mask;
      }
    }
  }, onRemove:function(entity, component) {
    component.remove();
  }});
  return {ModelComponentSystem:ModelComponentSystem};
}());
pc.extend(pc, function() {
  var ModelComponent = function ModelComponent(system, entity) {
    this.on("set_type", this.onSetType, this);
    this.on("set_asset", this.onSetAsset, this);
    this.on("set_castShadows", this.onSetCastShadows, this);
    this.on("set_receiveShadows", this.onSetReceiveShadows, this);
    this.on("set_castShadowsLightmap", this.onSetCastShadowsLightmap, this);
    this.on("set_lightmapped", this.onSetLightmapped, this);
    this.on("set_lightmapSizeMultiplier", this.onSetLightmapSizeMultiplier, this);
    this.on("set_isStatic", this.onSetIsStatic, this);
    this.on("set_model", this.onSetModel, this);
    this.on("set_material", this.onSetMaterial, this);
    this.on("set_mapping", this.onSetMapping, this);
    Object.defineProperty(this, "materialAsset", {set:this.setMaterialAsset.bind(this), get:this.getMaterialAsset.bind(this)});
    this._assetOld = 0;
    this._materialEvents = null;
    this._dirtyModelAsset = false;
    this._dirtyMaterialAsset = false;
    this._clonedModel = false;
  };
  ModelComponent = pc.inherits(ModelComponent, pc.Component);
  pc.extend(ModelComponent.prototype, {setVisible:function(visible) {
    console.warn("WARNING: setVisible: Function is deprecated. Set enabled property instead.");
    this.enabled = visible;
  }, _onAssetLoad:function(asset) {
    if (asset.resource) {
      this._onModelLoaded(asset.resource.clone());
      this._clonedModel = true;
    }
  }, _onAssetUnload:function(asset) {
    if (!this.model) {
      return;
    }
    this.system.app.scene.removeModel(this.model);
    var device = this.system.app.graphicsDevice;
    this.model = null;
  }, _onAssetChange:function(asset, attribute, newValue, oldValue) {
    if (attribute === "data") {
      this.mapping = this.data.mapping;
    }
  }, _onAssetRemove:function(asset) {
    if (this.asset === asset.id) {
      this.asset = null;
    }
  }, _setModelAsset:function(id) {
    if (this._assetOld === id) {
      return;
    }
    var assets = this.system.app.assets;
    var asset = id !== null ? assets.get(id) : null;
    this._dirtyModelAsset = true;
    this._onModelAsset(asset || null);
    if (!asset && id !== null) {
      assets.once("add:" + id, this._onModelAsset, this);
    }
  }, _onModelAsset:function(asset) {
    var assets = this.system.app.assets;
    if (this._assetOld) {
      assets.off("add:" + this._assetOld, this._onModelAsset, this);
      var assetOld = assets.get(this._assetOld);
      if (assetOld) {
        assetOld.off("load", this._onAssetLoad, this);
        assetOld.off("unload", this._onAssetUnload, this);
        assetOld.off("change", this._onAssetChange, this);
        assetOld.off("remove", this._onAssetRemove, this);
      }
    }
    this._assetOld = asset ? asset.id : 0;
    if (asset) {
      asset.on("load", this._onAssetLoad, this);
      asset.on("unload", this._onAssetUnload, this);
      asset.on("change", this._onAssetChange, this);
      asset.on("remove", this._onAssetRemove, this);
      if (asset.resource) {
        this._dirtyModelAsset = false;
        this._onModelLoaded(asset.resource.clone());
        this._clonedModel = true;
      } else {
        if (this.enabled && this.entity.enabled) {
          this._dirtyModelAsset = false;
          assets.load(asset);
        }
      }
    } else {
      this._dirtyModelAsset = false;
    }
  }, remove:function() {
    this._onModelAsset(null);
  }, _onModelLoaded:function(model) {
    if (this.data.type === "asset") {
      this.model = model;
    }
  }, onSetType:function(name, oldValue, newValue) {
    var data = this.data;
    if (newValue) {
      var mesh = null;
      this._area = null;
      if (newValue === "asset") {
        if (this.data.asset !== null) {
          this._setModelAsset(this.data.asset);
        } else {
          this.model = null;
        }
      } else {
        switch(newValue) {
          case "box":
            mesh = this.system.box;
            this._area = {x:2, y:2, z:2, uv:2 / 3};
            break;
          case "capsule":
            mesh = this.system.capsule;
            this._area = {x:Math.PI * 2, y:Math.PI, z:Math.PI * 2, uv:1 / 3 + 1 / 3 / 3 * 2};
            break;
          case "sphere":
            mesh = this.system.sphere;
            this._area = {x:Math.PI, y:Math.PI, z:Math.PI, uv:1};
            break;
          case "cone":
            mesh = this.system.cone;
            this._area = {x:2.54, y:2.54, z:2.54, uv:1 / 3 + 1 / 3 / 3};
            break;
          case "cylinder":
            mesh = this.system.cylinder;
            this._area = {x:Math.PI, y:.79 * 2, z:Math.PI, uv:1 / 3 + 1 / 3 / 3 * 2};
            break;
          case "plane":
            mesh = this.system.plane;
            this._area = {x:0, y:1, z:0, uv:1};
            break;
          default:
            throw new Error("Invalid model type: " + newValue);
        }
        var node = new pc.GraphNode;
        var model = new pc.Model;
        model.graph = node;
        model.meshInstances = [new pc.MeshInstance(node, mesh, data.material)];
        if (this.system._inTools) {
          model.generateWireframe();
        }
        this.model = model;
        this.asset = null;
      }
    }
  }, onSetAsset:function(name, oldValue, newValue) {
    var id = null;
    if (this.data.type === "asset") {
      if (newValue !== null) {
        id = newValue;
        if (newValue instanceof pc.Asset) {
          this.data.asset = newValue.id;
          id = newValue.id;
        }
      } else {
        this.model = null;
      }
    }
    if (id === null) {
      this.data.asset = null;
    }
    this._setModelAsset(id);
  }, onSetCastShadows:function(name, oldValue, newValue) {
    var model = this.data.model;
    if (model) {
      var scene = this.system.app.scene;
      var inScene = scene.containsModel(model);
      if (inScene && oldValue && !newValue) {
        scene.removeShadowCaster(model);
      }
      var meshInstances = model.meshInstances;
      for (var i = 0;i < meshInstances.length;i++) {
        meshInstances[i].castShadow = newValue;
      }
      if (inScene && !oldValue && newValue) {
        scene.addShadowCaster(model);
      }
    }
  }, onSetCastShadowsLightmap:function(name, oldValue, newValue) {
  }, onSetLightmapped:function(name, oldValue, newValue) {
    var i, m;
    if (this.data.model) {
      var rcv = this.data.model.meshInstances;
      if (newValue) {
        for (i = 0;i < rcv.length;i++) {
          m = rcv[i];
          m.mask = pc.MASK_BAKED;
        }
      } else {
        for (i = 0;i < rcv.length;i++) {
          m = rcv[i];
          m.deleteParameter("texture_lightMap");
          m.deleteParameter("texture_dirLightMap");
          m._shaderDefs &= ~pc.SHADERDEF_LM;
          m.mask = pc.MASK_DYNAMIC;
        }
      }
    }
  }, onSetLightmapSizeMultiplier:function(name, oldValue, newValue) {
    this.data.lightmapSizeMultiplier = newValue;
  }, onSetIsStatic:function(name, oldValue, newValue) {
    var i, m;
    if (this.data.model) {
      var rcv = this.data.model.meshInstances;
      for (i = 0;i < rcv.length;i++) {
        m = rcv[i];
        m.isStatic = newValue;
      }
    }
  }, onSetModel:function(name, oldValue, newValue) {
    if (oldValue) {
      this.system.app.scene.removeModel(oldValue);
      this.entity.removeChild(oldValue.getGraph());
      delete oldValue._entity;
      if (this._clonedModel) {
        oldValue.destroy();
        this._clonedModel = false;
      }
    }
    if (newValue) {
      var componentData = this.data;
      var meshInstances = newValue.meshInstances;
      for (var i = 0;i < meshInstances.length;i++) {
        meshInstances[i].castShadow = componentData.castShadows;
        meshInstances[i].receiveShadow = componentData.receiveShadows;
      }
      this.lightmapped = componentData.lightmapped;
      this.isStatic = componentData.isStatic;
      this.entity.addChild(newValue.graph);
      if (this.enabled && this.entity.enabled) {
        this.system.app.scene.addModel(newValue);
      }
      newValue._entity = this.entity;
      if (this.entity.animation) {
        this.entity.animation.setModel(newValue);
      }
      if (this.data.type === "asset") {
        this.mapping = this.data.mapping;
      } else {
        this._unsetMaterialEvents();
      }
    } else {
      this._unsetMaterialEvents();
    }
  }, _onMaterialAssetRemove:function(asset) {
    var assets = this.system.app.assets;
    var id = isNaN(asset) ? asset.id : asset;
    if (asset && isNaN(asset) && asset.resource === this.material) {
      this.material = pc.ModelHandler.DEFAULT_MATERIAL;
    }
    assets.off("add:" + id, this._onMaterialAssetAdd, this);
    assets.off("load:" + id, this._onMaterialAssetLoad, this);
    assets.off("unload:" + id, this._onMaterialAssetUnload, this);
    assets.off("remove:" + id, this._onMaterialAssetRemove, this);
  }, _onMaterialAssetAdd:function(asset) {
    var assets = this.system.app.assets;
    if (asset.resource) {
      this.material = asset.resource;
      this._dirtyMaterialAsset = false;
    } else {
      if (this.enabled && this.entity.enabled) {
        this._dirtyMaterialAsset = false;
        assets.load(asset);
      }
    }
  }, _onMaterialAssetLoad:function(asset) {
    var assets = this.system.app.assets;
    if (asset.resource) {
      this.material = asset.resource;
      this._dirtyMaterialAsset = false;
    } else {
      if (this.enabled && this.entity.enabled) {
        this._dirtyMaterialAsset = false;
        assets.load(asset);
      }
    }
  }, _onMaterialAssetUnload:function(asset) {
    var assets = this.system.app.assets;
    var id = isNaN(asset) ? asset.id : asset;
    if (asset && isNaN(asset) && asset.resource === this.material) {
      this.material = pc.ModelHandler.DEFAULT_MATERIAL;
    }
  }, setMaterialAsset:function(value) {
    this._dirtyMaterialAsset = true;
    var id = typeof value === "number" || !value ? value : value.id;
    var assets = this.system.app.assets;
    var self = this;
    if (this.data.materialAsset !== id) {
      if (this.data.materialAsset) {
        this._onMaterialAssetRemove(this.data.materialAsset);
      }
      if (id) {
        assets.on("load:" + id, this._onMaterialAssetLoad, this);
        assets.on("unload:" + id, this._onMaterialAssetUnload, this);
        assets.on("remove:" + id, this._onMaterialAssetRemove, this);
      }
    }
    if (id !== undefined && id !== null) {
      var asset = assets.get(id);
      if (asset) {
        this._onMaterialAssetLoad(asset);
      }
      assets.once("add:" + id, this._onMaterialAssetAdd, this);
    } else {
      if (id === null) {
        self.material = pc.ModelHandler.DEFAULT_MATERIAL;
        self._dirtyMaterialAsset = false;
      }
    }
    var valueOld = this.data.materialAsset;
    this.data.materialAsset = id;
    this.fire("set", "materialAsset", valueOld, id);
  }, getMaterialAsset:function() {
    return this.system.app.assets.get(this.data.materialAsset);
  }, onSetMaterial:function(name, oldValue, newValue) {
    if (newValue !== oldValue) {
      this.data.material = newValue;
      if (this.data.model && this.data.type !== "asset") {
        var meshInstances = this.data.model.meshInstances;
        for (var i = 0;i < meshInstances.length;i++) {
          meshInstances[i].material = newValue;
        }
      }
    }
  }, onSetMapping:function(name, oldValue, newValue) {
    if (this.data.type !== "asset" || !this.data.model) {
      return;
    }
    this._unsetMaterialEvents();
    if (!newValue) {
      newValue = {};
    }
    var meshInstances = this.data.model.meshInstances;
    var modelAsset = this.asset ? this.system.app.assets.get(this.asset) : null;
    var assetMapping = modelAsset ? modelAsset.data.mapping : null;
    for (var i = 0, len = meshInstances.length;i < len;i++) {
      if (newValue[i] !== undefined) {
        if (newValue[i]) {
          this._loadAndSetMeshInstanceMaterial(newValue[i], meshInstances[i], i);
        } else {
          meshInstances[i].material = pc.ModelHandler.DEFAULT_MATERIAL;
        }
      } else {
        if (assetMapping) {
          if (assetMapping[i] && (assetMapping[i].material || assetMapping[i].path)) {
            var idOrPath = assetMapping[i].material || assetMapping[i].path;
            this._loadAndSetMeshInstanceMaterial(idOrPath, meshInstances[i], i);
          } else {
            meshInstances[i].material = pc.ModelHandler.DEFAULT_MATERIAL;
          }
        }
      }
    }
  }, _setMaterialEvent:function(index, event, id, handler) {
    var evt = event + ":" + id;
    this.system.app.assets.on(evt, handler, this);
    if (!this._materialEvents) {
      this._materialEvents = [];
    }
    if (!this._materialEvents[index]) {
      this._materialEvents[index] = {};
    }
    this._materialEvents[index][evt] = {id:id, handler:handler};
  }, _unsetMaterialEvents:function() {
    var assets = this.system.app.assets;
    var events = this._materialEvents;
    if (!events) {
      return;
    }
    for (var i = 0, len = events.length;i < len;i++) {
      if (!events[i]) {
        continue;
      }
      var evt = events[i];
      for (var key in evt) {
        assets.off(key, evt[key].handler, this);
      }
    }
    this._materialEvents = null;
  }, _getAssetByIdOrPath:function(idOrPath) {
    var asset = null;
    var isPath = isNaN(parseInt(idOrPath, 10));
    if (!isPath) {
      asset = this.system.app.assets.get(idOrPath);
    } else {
      if (this.asset) {
        var url = this._getMaterialAssetUrl(idOrPath);
        if (url) {
          asset = this.system.app.assets.getByUrl(url);
        }
      }
    }
    return asset;
  }, _getMaterialAssetUrl:function(path) {
    if (!this.asset) {
      return null;
    }
    var modelAsset = this.system.app.assets.get(this.asset);
    if (!modelAsset) {
      return null;
    }
    var fileUrl = modelAsset.getFileUrl();
    var dirUrl = pc.path.getDirectory(fileUrl);
    return pc.path.join(dirUrl, path);
  }, _loadAndSetMeshInstanceMaterial:function(idOrPath, meshInstance, index) {
    var self = this;
    var assets = this.system.app.assets;
    var asset = this._getAssetByIdOrPath(idOrPath);
    if (!asset) {
      return;
    }
    var handleMaterial = function(asset) {
      if (asset.resource) {
        meshInstance.material = asset.resource;
        self._setMaterialEvent(index, "remove", asset.id, function() {
          meshInstance.material = pc.ModelHandler.DEFAULT_MATERIAL;
        });
      } else {
        self._setMaterialEvent(index, "load", asset.id, function(asset) {
          meshInstance.material = asset.resource;
          self._setMaterialEvent(index, "remove", asset.id, function() {
            meshInstance.material = pc.ModelHandler.DEFAULT_MATERIAL;
          });
        });
        if (self.enabled && self.entity.enabled) {
          assets.load(asset);
        }
      }
    };
    if (asset) {
      handleMaterial(asset);
    } else {
      meshInstance.material = pc.ModelHandler.DEFAULT_MATERIAL;
      var isPath = isNaN(parseInt(idOrPath, 10));
      self._setMaterialEvent(index, isPath ? "add:url" : "add", idOrPath, handleMaterial);
    }
  }, onSetReceiveShadows:function(name, oldValue, newValue) {
    if (newValue !== undefined) {
      var componentData = this.data;
      if (componentData.model) {
        var meshInstances = componentData.model.meshInstances;
        for (var i = 0;i < meshInstances.length;i++) {
          meshInstances[i].receiveShadow = newValue;
        }
      }
    }
  }, onEnable:function() {
    ModelComponent._super.onEnable.call(this);
    var model = this.data.model;
    var isAsset = this.data.type === "asset";
    if (model) {
      var inScene = this.system.app.scene.containsModel(model);
      if (!inScene) {
        this.system.app.scene.addModel(model);
      }
    } else {
      if (isAsset && this._dirtyModelAsset) {
        var asset = this.data.asset;
        if (!asset) {
          return;
        }
        asset = this.system.app.assets.get(asset);
        if (asset) {
          this._onModelAsset(asset);
        }
      }
    }
    if (this._dirtyMaterialAsset) {
      var materialAsset = this.data.materialAsset;
      if (materialAsset) {
        materialAsset = this.system.app.assets.get(materialAsset);
        if (materialAsset && !materialAsset.resource) {
          this._onMaterialAssetLoad(materialAsset);
        }
      }
    }
    if (isAsset) {
      var mapping = this.data.mapping;
      if (mapping) {
        for (var index in mapping) {
          if (mapping[index]) {
            var asset = this._getAssetByIdOrPath(mapping[index]);
            if (asset && !asset.resource) {
              this.system.app.assets.load(asset);
            }
          }
        }
      }
    }
  }, onDisable:function() {
    ModelComponent._super.onDisable.call(this);
    var model = this.data.model;
    if (model) {
      var inScene = this.system.app.scene.containsModel(model);
      if (inScene) {
        this.system.app.scene.removeModel(model);
      }
    }
  }, hide:function() {
    var model = this.data.model;
    if (model) {
      var i, l;
      var instances = model.meshInstances;
      for (i = 0, l = instances.length;i < l;i++) {
        instances[i].visible = false;
      }
    }
  }, show:function() {
    var model = this.data.model;
    if (model) {
      var i, l;
      var instances = model.meshInstances;
      for (i = 0, l = instances.length;i < l;i++) {
        instances[i].visible = true;
      }
    }
  }});
  Object.defineProperty(ModelComponent.prototype, "meshInstances", {get:function() {
    if (!this.model) {
      return null;
    }
    return this.model.meshInstances;
  }, set:function(value) {
    if (!this.model) {
      return;
    }
    this.model.meshInstances = value;
  }});
  return {ModelComponent:ModelComponent};
}());
pc.extend(pc, function() {
  var ModelComponentData = function() {
    this.enabled = true;
    this.type = "asset";
    this.asset = null;
    this.castShadows = true;
    this.receiveShadows = true;
    this.materialAsset = null;
    this.mapping = null;
    this.castShadowsLightmap = true;
    this.lightmapped = false;
    this.lightmapSizeMultiplier = 1;
    this.isStatic = false;
    this.material = null;
    this.model = null;
  };
  ModelComponentData = pc.inherits(ModelComponentData, pc.ComponentData);
  return {ModelComponentData:ModelComponentData};
}());
pc.extend(pc, function() {
  var CameraComponentSystem = function(app) {
    this.id = "camera";
    this.description = "Renders the scene from the location of the Entity.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.CameraComponent;
    this.DataType = pc.CameraComponentData;
    this.schema = ["enabled", "clearColorBuffer", "clearColor", "clearDepthBuffer", "clearStencilBuffer", "frustumCulling", "projection", "fov", "orthoHeight", "nearClip", "farClip", "priority", "rect", "camera", "aspectRatio", "horizontalFov", "model", "renderTarget"];
    this.cameras = [];
    this.on("beforeremove", this.onBeforeRemove, this);
    this.on("remove", this.onRemove, this);
    pc.ComponentSystem.on("update", this.onUpdate, this);
  };
  CameraComponentSystem = pc.inherits(CameraComponentSystem, pc.ComponentSystem);
  pc.extend(CameraComponentSystem.prototype, {initializeComponentData:function(component, _data, properties) {
    properties = ["postEffects", "enabled", "model", "camera", "aspectRatio", "horizontalFov", "renderTarget", "clearColor", "fov", "orthoHeight", "nearClip", "farClip", "projection", "priority", "clearColorBuffer", "clearDepthBuffer", "clearStencilBuffer", "frustumCulling", "rect"];
    var data = {};
    properties.forEach(function(prop) {
      data[prop] = _data[prop];
    });
    if (data.clearColor && pc.type(data.clearColor) === "array") {
      var c = data.clearColor;
      data.clearColor = new pc.Color(c[0], c[1], c[2], c[3]);
    }
    if (data.rect && pc.type(data.rect) === "array") {
      var rect = data.rect;
      data.rect = new pc.Vec4(rect[0], rect[1], rect[2], rect[3]);
    }
    if (data.activate) {
      console.warn("WARNING: activate: Property is deprecated. Set enabled property instead.");
      data.enabled = data.activate;
    }
    data.camera = new pc.Camera;
    data._node = component.entity;
    data.postEffects = new pc.PostEffectQueue(this.app, component);
    CameraComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, onBeforeRemove:function(entity, component) {
    this.removeCamera(component);
  }, onRemove:function(entity, data) {
    data.camera = null;
  }, onUpdate:function(dt) {
    var components = this.store;
    var component, componentData, cam, vrDisplay;
    if (this.app.vr) {
      for (var id in components) {
        component = components[id];
        componentData = component.data;
        cam = componentData.camera;
        vrDisplay = cam.vrDisplay;
        if (componentData.enabled && component.entity.enabled && vrDisplay) {
          vrDisplay.setClipPlanes(cam._nearClip, cam._farClip);
          if (cam._node) {
            cam._node.localTransform.copy(vrDisplay.combinedViewInv);
            cam._node.dirtyLocal = false;
            cam._node.dirtyWorld = true;
            cam._node.syncHierarchy();
          }
        }
      }
    }
  }, addCamera:function(camera) {
    this.cameras.push(camera);
    this.sortCamerasByPriority();
  }, removeCamera:function(camera) {
    var index = this.cameras.indexOf(camera);
    if (index >= 0) {
      this.cameras.splice(index, 1);
      this.sortCamerasByPriority();
    }
  }, sortCamerasByPriority:function() {
    this.cameras.sort(function(a, b) {
      return a.priority - b.priority;
    });
  }});
  return {CameraComponentSystem:CameraComponentSystem};
}());
pc.extend(pc, function() {
  var CameraComponent = function CameraComponent(system, entity) {
    this.on("set_aspectRatio", this.onSetAspectRatio, this);
    this.on("set_camera", this.onSetCamera, this);
    this.on("set_clearColor", this.onSetClearColor, this);
    this.on("set_fov", this.onSetFov, this);
    this.on("set_orthoHeight", this.onSetOrthoHeight, this);
    this.on("set_nearClip", this.onSetNearClip, this);
    this.on("set_farClip", this.onSetFarClip, this);
    this.on("set_projection", this.onSetProjection, this);
    this.on("set_priority", this.onSetPriority, this);
    this.on("set_clearColorBuffer", this.updateClearFlags, this);
    this.on("set_clearDepthBuffer", this.updateClearFlags, this);
    this.on("set_clearStencilBuffer", this.updateClearFlags, this);
    this.on("set_renderTarget", this.onSetRenderTarget, this);
    this.on("set_rect", this.onSetRect, this);
    this.on("set_horizontalFov", this.onSetHorizontalFov, this);
    this.on("set_frustumCulling", this.onSetFrustumCulling, this);
  };
  CameraComponent = pc.inherits(CameraComponent, pc.Component);
  Object.defineProperty(CameraComponent.prototype, "projectionMatrix", {get:function() {
    return this.data.camera.getProjectionMatrix();
  }});
  Object.defineProperty(CameraComponent.prototype, "viewMatrix", {get:function() {
    var wtm = this.data.camera._node.getWorldTransform();
    return wtm.clone().invert();
  }});
  Object.defineProperty(CameraComponent.prototype, "frustum", {get:function() {
    return this.data.camera.frustum;
  }});
  Object.defineProperty(CameraComponent.prototype, "vrDisplay", {get:function() {
    return this.data.camera.vrDisplay;
  }, set:function(value) {
    this.data.camera.vrDisplay = value;
    if (value) {
      value._camera = this.data.camera;
    }
  }});
  pc.extend(CameraComponent.prototype, {screenToWorld:function(screenx, screeny, cameraz, worldCoord) {
    var device = this.system.app.graphicsDevice;
    return this.data.camera.screenToWorld(screenx, screeny, cameraz, device.clientRect.width, device.clientRect.height, worldCoord);
  }, worldToScreen:function(worldCoord, screenCoord) {
    var device = this.system.app.graphicsDevice;
    return this.data.camera.worldToScreen(worldCoord, device.clientRect.width, device.clientRect.height, screenCoord);
  }, onSetAspectRatio:function(name, oldValue, newValue) {
    this.data.camera.aspectRatio = newValue;
  }, onSetCamera:function(name, oldValue, newValue) {
    if (oldValue) {
      oldValue._node = null;
    }
    newValue._node = this.entity;
  }, onSetClearColor:function(name, oldValue, newValue) {
    this.data.camera.clearColor[0] = newValue.data[0];
    this.data.camera.clearColor[1] = newValue.data[1];
    this.data.camera.clearColor[2] = newValue.data[2];
    this.data.camera.clearColor[3] = newValue.data[3];
  }, onSetFov:function(name, oldValue, newValue) {
    this.data.camera.fov = newValue;
  }, onSetOrthoHeight:function(name, oldValue, newValue) {
    this.data.camera.orthoHeight = newValue;
  }, onSetNearClip:function(name, oldValue, newValue) {
    this.data.camera.nearClip = newValue;
  }, onSetFarClip:function(name, oldValue, newValue) {
    this.data.camera.farClip = newValue;
  }, onSetHorizontalFov:function(name, oldValue, newValue) {
    this.data.camera.horizontalFov = newValue;
  }, onSetFrustumCulling:function(name, oldValue, newValue) {
    this.data.camera.frustumCulling = newValue;
  }, onSetProjection:function(name, oldValue, newValue) {
    this.data.camera.projection = newValue;
  }, onSetPriority:function(name, oldValue, newValue) {
    this.system.sortCamerasByPriority();
  }, updateClearFlags:function() {
    var flags = 0;
    if (this.clearColorBuffer) {
      flags = flags | pc.CLEARFLAG_COLOR;
    }
    if (this.clearDepthBuffer) {
      flags = flags | pc.CLEARFLAG_DEPTH;
    }
    if (this.clearStencilBuffer) {
      flags = flags | pc.CLEARFLAG_STENCIL;
    }
    this.data.camera.clearFlags = flags;
  }, onSetRenderTarget:function(name, oldValue, newValue) {
    this.data.camera.renderTarget = newValue;
  }, onSetRect:function(name, oldValue, newValue) {
    this.data.camera.setRect(newValue.data[0], newValue.data[1], newValue.data[2], newValue.data[3]);
    this._resetAspectRatio();
  }, onEnable:function() {
    CameraComponent._super.onEnable.call(this);
    this.system.addCamera(this);
    this.postEffects.enable();
  }, onDisable:function() {
    CameraComponent._super.onDisable.call(this);
    this.postEffects.disable();
    this.system.removeCamera(this);
  }, _resetAspectRatio:function() {
    var camera = this.camera;
    if (camera) {
      if (camera.renderTarget) {
        return;
      }
      var device = this.system.app.graphicsDevice;
      var rect = this.rect;
      camera.aspectRatio = device.width * rect.z / (device.height * rect.w);
    }
  }, frameBegin:function() {
    this._resetAspectRatio();
    this.data.isRendering = true;
  }, frameEnd:function() {
    this.data.isRendering = false;
  }, enterVr:function(display, callback) {
    if (display instanceof Function && !callback) {
      callback = display;
      display = null;
    }
    if (!this.system.app.vr) {
      callback("VrManager not created. Enable VR in project settings.");
      return;
    }
    if (!display) {
      display = this.system.app.vr.display;
    }
    if (display) {
      var self = this;
      if (display.capabilities.canPresent) {
        display.requestPresent(function(err) {
          if (!err) {
            self.vrDisplay = display;
            self.vrDisplay.once("beforepresentchange", function(display) {
              if (!display.presenting) {
                self.vrDisplay = null;
              }
            });
          }
          callback(err);
        });
      } else {
        self.vrDisplay = display;
        callback();
      }
    } else {
      callback("No pc.VrDisplay to present");
    }
  }, exitVr:function(callback) {
    if (this.vrDisplay) {
      if (this.vrDisplay.capabilities.canPresent) {
        var display = this.vrDisplay;
        this.vrDisplay = null;
        display.exitPresent(callback);
      } else {
        this.vrDisplay = null;
        callback();
      }
    } else {
      callback("Not presenting VR");
    }
  }});
  return {CameraComponent:CameraComponent};
}());
pc.extend(pc, function() {
  var CameraComponentData = function() {
    this.clearColor = new pc.Color(.722, .722, .722, 1);
    this.clearColorBuffer = true;
    this.clearDepthBuffer = true;
    this.clearStencilBuffer = true;
    this.nearClip = .1;
    this.farClip = 1E3;
    this.fov = 45;
    this.orthoHeight = 100;
    this.projection = pc.PROJECTION_PERSPECTIVE;
    this.priority = 0;
    this.rect = new pc.Vec4(0, 0, 1, 1);
    this.enabled = true;
    this.frustumCulling = false;
    this.camera = null;
    this.aspectRatio = 16 / 9;
    this.renderTarget = null;
    this.postEffects = null;
    this.isRendering = false;
  };
  CameraComponentData = pc.inherits(CameraComponentData, pc.ComponentData);
  return {CameraComponentData:CameraComponentData};
}());
pc.extend(pc, function() {
  function PostEffectQueue(app, camera) {
    var self = this;
    this.app = app;
    this.camera = camera;
    this.effects = [];
    this.enabled = false;
    this.depthTarget = null;
    this.renderTargetScale = 1;
    this.resizeTimeout = null;
    this.resizeLast = 0;
    this._resizeTimeoutCallback = function() {
      self.resizeRenderTargets();
    };
    camera.on("set_rect", this.onCameraRectChanged, this);
  }
  PostEffectQueue.prototype = {_createOffscreenTarget:function(useDepth, hdr) {
    var rect = this.camera.rect;
    var width = Math.floor(rect.z * this.app.graphicsDevice.width * this.renderTargetScale);
    var height = Math.floor(rect.w * this.app.graphicsDevice.height * this.renderTargetScale);
    var device = this.app.graphicsDevice;
    var format = hdr ? device.getHdrFormat() : pc.PIXELFORMAT_R8_G8_B8_A8;
    var colorBuffer = new pc.Texture(device, {format:format, width:width, height:height});
    colorBuffer.minFilter = pc.FILTER_NEAREST;
    colorBuffer.magFilter = pc.FILTER_NEAREST;
    colorBuffer.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
    colorBuffer.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
    return new pc.RenderTarget(this.app.graphicsDevice, colorBuffer, {depth:useDepth});
  }, setRenderTargetScale:function(scale) {
    this.renderTargetScale = scale;
    this.resizeRenderTargets();
  }, addEffect:function(effect) {
    var isFirstEffect = this.effects.length === 0;
    var effects = this.effects;
    var newEntry = {effect:effect, inputTarget:this._createOffscreenTarget(isFirstEffect, effect.hdr), outputTarget:null};
    if (isFirstEffect) {
      this.camera.renderTarget = newEntry.inputTarget;
    }
    effects.push(newEntry);
    var len = effects.length;
    if (len > 1) {
      effects[len - 2].outputTarget = newEntry.inputTarget;
    }
    this.enable();
  }, removeEffect:function(effect) {
    var index = -1;
    for (var i = 0, len = this.effects.length;i < len;i++) {
      if (this.effects[i].effect === effect) {
        index = i;
        break;
      }
    }
    if (index >= 0) {
      if (index > 0) {
        this.effects[index - 1].outputTarget = index + 1 < this.effects.length ? this.effects[index + 1].inputTarget : null;
      } else {
        if (this.effects.length > 1) {
          if (!this.effects[1].inputTarget._depth) {
            this.effects[1].inputTarget.destroy();
            this.effects[1].inputTarget = this._createOffscreenTarget(true, this.effects[1].hdr);
          }
          this.camera.renderTarget = this.effects[1].inputTarget;
        }
      }
      this.effects[index].inputTarget.destroy();
      this.effects.splice(index, 1);
    }
    if (this.enabled) {
      if (effect.needsDepthBuffer) {
        this.camera.releaseDepthMap();
      }
    }
    if (this.effects.length === 0) {
      this.disable();
    }
  }, requestDepthMap:function() {
    for (var i = 0, len = this.effects.length;i < len;i++) {
      var effect = this.effects[i].effect;
      if (effect.needsDepthBuffer) {
        this.camera.camera.requestDepthMap();
      }
    }
  }, releaseDepthMap:function() {
    for (var i = 0, len = this.effects.length;i < len;i++) {
      var effect = this.effects[i].effect;
      if (effect.needsDepthBuffer) {
        this.camera.releaseDepthMap();
      }
    }
  }, destroy:function() {
    for (var i = 0, len = this.effects.length;i < len;i++) {
      this.effects[i].inputTarget.destroy();
    }
    this.effects.length = 0;
    this.disable();
  }, enable:function() {
    if (!this.enabled && this.effects.length) {
      this.enabled = true;
      var self = this;
      this.requestDepthMap();
      this.app.graphicsDevice.on("resizecanvas", this._onCanvasResized, this);
      self.camera.camera.setRect(0, 0, 1, 1);
      this.command = new pc.Command(pc.LAYER_FX, pc.BLEND_NONE, function() {
        if (self.enabled && self.camera.data.isRendering) {
          var rect = null;
          var len = self.effects.length;
          if (len) {
            self.camera.renderTarget = self.effects[0].inputTarget;
            self.depthTarget = self.camera.camera._depthTarget;
            for (var i = 0;i < len;i++) {
              var fx = self.effects[i];
              if (self.depthTarget) {
                fx.effect.depthMap = self.depthTarget.colorBuffer;
              }
              if (i === len - 1) {
                rect = self.camera.rect;
              }
              fx.effect.render(fx.inputTarget, fx.outputTarget, rect);
            }
          }
        }
      });
      this.app.scene.drawCalls.push(this.command);
    }
  }, disable:function() {
    if (this.enabled) {
      this.enabled = false;
      this.app.graphicsDevice.off("resizecanvas", this._onCanvasResized, this);
      this.camera.renderTarget = null;
      this.releaseDepthMap();
      var rect = this.camera.rect;
      this.camera.camera.setRect(rect.x, rect.y, rect.z, rect.w);
      var i = this.app.scene.drawCalls.indexOf(this.command);
      if (i >= 0) {
        this.app.scene.drawCalls.splice(i, 1);
      }
    }
  }, _onCanvasResized:function(width, height) {
    var rect = this.camera.rect;
    var device = this.app.graphicsDevice;
    this.camera.camera.aspectRatio = device.width * rect.z / (device.height * rect.w);
    if (this.resizeTimeout) {
      return;
    }
    if (pc.now() - this.resizeLast > 100) {
      this.resizeRenderTargets();
    } else {
      this.resizeTimeout = setTimeout(this._resizeTimeoutCallback, 100);
    }
  }, resizeRenderTargets:function() {
    if (this.resizeTimeout) {
      clearTimeout(this.resizeTimeout);
      this.resizeTimeout = null;
    }
    this.resizeLast = pc.now();
    var rect = this.camera.rect;
    var desiredWidth = Math.floor(rect.z * this.app.graphicsDevice.width * this.renderTargetScale);
    var desiredHeight = Math.floor(rect.w * this.app.graphicsDevice.height * this.renderTargetScale);
    var effects = this.effects;
    for (var i = 0, len = effects.length;i < len;i++) {
      var fx = effects[i];
      if (fx.inputTarget.width !== desiredWidth || fx.inputTarget.height !== desiredHeight) {
        fx.inputTarget.destroy();
        fx.inputTarget = this._createOffscreenTarget(fx.effect.needsDepthBuffer || i === 0, fx.hdr);
        if (i > 0) {
          effects[i - 1].outputTarget = fx.inputTarget;
        } else {
          this.camera.renderTarget = fx.inputTarget;
        }
      }
    }
  }, onCameraRectChanged:function(name, oldValue, newValue) {
    if (this.enabled) {
      this.camera.camera.setRect(0, 0, 1, 1);
      this.resizeRenderTargets();
    }
  }};
  return {PostEffectQueue:PostEffectQueue};
}());
pc.extend(pc, function() {
  var lightTypes = {"directional":pc.LIGHTTYPE_DIRECTIONAL, "point":pc.LIGHTTYPE_POINT, "spot":pc.LIGHTTYPE_SPOT};
  var LightComponentSystem = function(app) {
    this.id = "light";
    this.description = "Enables the Entity to emit light.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.LightComponent;
    this.DataType = pc.LightComponentData;
    this.on("remove", this.onRemove, this);
  };
  LightComponentSystem = pc.inherits(LightComponentSystem, pc.ComponentSystem);
  pc.extend(LightComponentSystem.prototype, {initializeComponentData:function(component, _data) {
    var data = {};
    var _props = pc._lightProps;
    var name;
    for (var i = 0;i < _props.length;i++) {
      name = _props[i];
      data[name] = _data[name];
    }
    if (!data.type) {
      data.type = component.data.type;
    }
    component.data.type = data.type;
    if (data.color && pc.type(data.color) === "array") {
      data.color = new pc.Color(data.color[0], data.color[1], data.color[2]);
    }
    if (data.cookieOffset && data.cookieOffset instanceof Array) {
      data.cookieOffset = new pc.Vec2(data.cookieOffset[0], data.cookieOffset[1]);
    }
    if (data.cookieScale && data.cookieScale instanceof Array) {
      data.cookieScale = new pc.Vec2(data.cookieScale[0], data.cookieScale[1]);
    }
    if (data.enable) {
      console.warn("WARNING: enable: Property is deprecated. Set enabled property instead.");
      data.enabled = data.enable;
    }
    var light = new pc.Light;
    light.type = lightTypes[data.type];
    light._node = component.entity;
    this.app.scene.addLight(light);
    component.data.light = light;
    LightComponentSystem._super.initializeComponentData.call(this, component, data, _props);
  }, onRemove:function(entity, data) {
    this.app.scene.removeLight(data.light);
  }, cloneComponent:function(entity, clone) {
    var light = entity.light;
    var data = [];
    var name;
    var _props = pc._lightProps;
    for (var i = 0;i < _props.length;i++) {
      name = _props[i];
      if (name === "light") {
        continue;
      }
      if (light[name] && light[name].clone) {
        data[name] = light[name].clone();
      } else {
        data[name] = light[name];
      }
    }
    this.addComponent(clone, data);
  }, changeType:function(component, oldValue, newValue) {
    if (oldValue !== newValue) {
      component.light.type = lightTypes[newValue];
    }
  }});
  return {LightComponentSystem:LightComponentSystem};
}());
pc.extend(pc, function() {
  var _props = [];
  var _propsDefault = [];
  function _defineProperty(name, defaultValue, setFunc) {
    var c = LightComponent.prototype;
    _props.push(name);
    _propsDefault.push(defaultValue);
    Object.defineProperty(c, name, {get:function() {
      return this.data[name];
    }, set:function(value) {
      var data = this.data;
      var oldValue = data[name];
      if (oldValue === value) {
        return;
      }
      data[name] = value;
      if (setFunc) {
        setFunc.call(this, value, oldValue);
      }
    }, configurable:true});
  }
  var LightComponent = function LightComponent(system, entity) {
    this._cookieAsset = null;
    this._cookieAssetId = null;
    this._cookieAssetAdd = false;
    this._cookieMatrix = null;
  };
  LightComponent = pc.inherits(LightComponent, pc.Component);
  var _defineProps = function(c, d, s) {
    _defineProperty("enabled", true, function(newValue, oldValue) {
      this.onSetEnabled(null, oldValue, newValue);
    });
    _defineProperty("light", null);
    _defineProperty("type", "directional", function(newValue, oldValue) {
      this.system.changeType(this, oldValue, newValue);
      this.refreshProperties();
    });
    _defineProperty("color", new pc.Color(1, 1, 1), function(newValue, oldValue) {
      this.light.setColor(newValue);
    });
    _defineProperty("intensity", 1, function(newValue, oldValue) {
      this.light.intensity = newValue;
    });
    _defineProperty("castShadows", false, function(newValue, oldValue) {
      this.light.castShadows = newValue;
    });
    _defineProperty("shadowDistance", 40, function(newValue, oldValue) {
      this.light.shadowDistance = newValue;
    });
    _defineProperty("shadowResolution", 1024, function(newValue, oldValue) {
      this.light.shadowResolution = newValue;
    });
    _defineProperty("shadowBias", .05, function(newValue, oldValue) {
      this.light.shadowBias = -.01 * newValue;
    });
    _defineProperty("normalOffsetBias", 0, function(newValue, oldValue) {
      this.light.normalOffsetBias = newValue;
    });
    _defineProperty("range", 10, function(newValue, oldValue) {
      this.light.attenuationEnd = newValue;
    });
    _defineProperty("innerConeAngle", 40, function(newValue, oldValue) {
      this.light.innerConeAngle = newValue;
    });
    _defineProperty("outerConeAngle", 45, function(newValue, oldValue) {
      this.light.outerConeAngle = newValue;
    });
    _defineProperty("falloffMode", pc.LIGHTFALLOFF_LINEAR, function(newValue, oldValue) {
      this.light.falloffMode = newValue;
    });
    _defineProperty("shadowType", pc.SHADOW_DEPTH, function(newValue, oldValue) {
      this.light.shadowType = newValue;
    });
    _defineProperty("vsmBlurSize", 11, function(newValue, oldValue) {
      this.light.vsmBlurSize = newValue;
    });
    _defineProperty("vsmBlurMode", pc.BLUR_GAUSSIAN, function(newValue, oldValue) {
      this.light.vsmBlurMode = newValue;
    });
    _defineProperty("vsmBias", .01 * .25, function(newValue, oldValue) {
      this.light.vsmBias = newValue;
    });
    _defineProperty("cookieAsset", null, function(newValue, oldValue) {
      if (this._cookieAssetId && (newValue instanceof pc.Asset && newValue.id === this._cookieAssetId || newValue === this._cookieAssetId)) {
        return;
      }
      this.onCookieAssetRemove();
      this._cookieAssetId = null;
      if (newValue instanceof pc.Asset) {
        this.data.cookieAsset = newValue.id;
        this._cookieAssetId = newValue.id;
        this.onCookieAssetAdd(newValue);
      } else {
        if (typeof newValue === "number") {
          this._cookieAssetId = newValue;
          var asset = this.system.app.assets.get(newValue);
          if (asset) {
            this.onCookieAssetAdd(asset);
          } else {
            this._cookieAssetAdd = true;
            this.system.app.assets.on("add:" + this._cookieAssetId, this.onCookieAssetAdd, this);
          }
        }
      }
    });
    _defineProperty("cookie", null, function(newValue, oldValue) {
      this.light.cookie = newValue;
    });
    _defineProperty("cookieIntensity", 1, function(newValue, oldValue) {
      this.light.cookieIntensity = newValue;
    });
    _defineProperty("cookieFalloff", true, function(newValue, oldValue) {
      this.light.cookieFalloff = newValue;
    });
    _defineProperty("cookieChannel", "rgb", function(newValue, oldValue) {
      this.light.cookieChannel = newValue;
    });
    _defineProperty("cookieAngle", 0, function(newValue, oldValue) {
      if (newValue !== 0 || this.cookieScale !== null) {
        if (!this._cookieMatrix) {
          this._cookieMatrix = new pc.Vec4;
        }
        var scx = 1;
        var scy = 1;
        if (this.cookieScale) {
          scx = this.cookieScale.x;
          scy = this.cookieScale.y;
        }
        var c = Math.cos(newValue * pc.math.DEG_TO_RAD);
        var s = Math.sin(newValue * pc.math.DEG_TO_RAD);
        this._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy);
        this.light.cookieTransform = this._cookieMatrix;
      } else {
        this.light.cookieTransform = null;
      }
    });
    _defineProperty("cookieScale", null, function(newValue, oldValue) {
      if (newValue !== null || this.cookieAngle !== 0) {
        if (!this._cookieMatrix) {
          this._cookieMatrix = new pc.Vec4;
        }
        var scx = newValue.x;
        var scy = newValue.y;
        var c = Math.cos(this.cookieAngle * pc.math.DEG_TO_RAD);
        var s = Math.sin(this.cookieAngle * pc.math.DEG_TO_RAD);
        this._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy);
        this.light.cookieTransform = this._cookieMatrix;
      } else {
        this.light.cookieTransform = null;
      }
    });
    _defineProperty("cookieOffset", null, function(newValue, oldValue) {
      this.light.cookieOffset = newValue;
    });
    _defineProperty("shadowUpdateMode", pc.SHADOWUPDATE_REALTIME, function(newValue, oldValue) {
      this.light.shadowUpdateMode = newValue;
    });
    _defineProperty("mask", 1, function(newValue, oldValue) {
      this.light.mask = newValue;
    });
    _defineProperty("affectDynamic", true, function(newValue, oldValue) {
      if (newValue) {
        this.light.mask |= pc.MASK_DYNAMIC;
      } else {
        this.light.mask &= ~pc.MASK_DYNAMIC;
      }
      this.light.mask = this.light._mask;
    });
    _defineProperty("affectLightmapped", false, function(newValue, oldValue) {
      if (newValue) {
        this.light.mask |= pc.MASK_BAKED;
        if (this.bake) {
          this.light.mask &= ~pc.MASK_LIGHTMAP;
        }
      } else {
        this.light.mask &= ~pc.MASK_BAKED;
        if (this.bake) {
          this.light.mask |= pc.MASK_LIGHTMAP;
        }
      }
      this.light.mask = this.light._mask;
    });
    _defineProperty("bake", false, function(newValue, oldValue) {
      if (newValue) {
        this.light.mask |= pc.MASK_LIGHTMAP;
        if (this.affectLightmapped) {
          this.light.mask &= ~pc.MASK_BAKED;
        }
      } else {
        this.light.mask &= ~pc.MASK_LIGHTMAP;
        if (this.affectLightmapped) {
          this.light.mask |= pc.MASK_BAKED;
        }
      }
      this.light.mask = this.light._mask;
    });
    _defineProperty("bakeDir", true, function(newValue, oldValue) {
      this.light.bakeDir = newValue;
    });
    _defineProperty("isStatic", false, function(newValue, oldValue) {
      this.light.isStatic = newValue;
    });
  };
  _defineProps();
  Object.defineProperty(LightComponent.prototype, "enable", {get:function() {
    console.warn("WARNING: enable: Property is deprecated. Query enabled property instead.");
    return this.enabled;
  }, set:function(value) {
    console.warn("WARNING: enable: Property is deprecated. Set enabled property instead.");
    this.enabled = value;
  }});
  pc.extend(LightComponent.prototype, {refreshProperties:function() {
    var name;
    for (var i = 0;i < _props.length;i++) {
      name = _props[i];
      this[name] = this[name];
    }
    if (this.enabled && this.entity.enabled) {
      this.onEnable();
    }
  }, updateShadow:function() {
    this.light.updateShadow();
  }, onCookieAssetSet:function() {
    var forceLoad = false;
    if (this._cookieAsset.type === "cubemap" && !this._cookieAsset.loadFaces) {
      this._cookieAsset.loadFaces = true;
      forceLoad = true;
    }
    if (!this._cookieAsset.resource || forceLoad) {
      this.system.app.assets.load(this._cookieAsset);
    }
    if (this._cookieAsset.resource) {
      this.onCookieAssetLoad();
    }
  }, onCookieAssetAdd:function(asset) {
    if (!this._cookieAssetId === asset.id) {
      return;
    }
    this._cookieAsset = asset;
    if (this.light._enabled) {
      this.onCookieAssetSet();
    }
    this._cookieAsset.on("load", this.onCookieAssetLoad, this);
    this._cookieAsset.on("remove", this.onCookieAssetRemove, this);
  }, onCookieAssetLoad:function() {
    if (!this._cookieAsset || !this._cookieAsset.resource) {
      return;
    }
    this.cookie = this._cookieAsset.resource;
  }, onCookieAssetRemove:function() {
    if (!this._cookieAssetId) {
      return;
    }
    if (this._cookieAssetAdd) {
      this.system.app.assets.off("add:" + this._cookieAssetId, this.onCookieAssetAdd, this);
      this._cookieAssetAdd = false;
    }
    if (this._cookieAsset) {
      this._cookieAsset.off("load", this.onCookieAssetLoad, this);
      this._cookieAsset.off("remove", this.onCookieAssetRemove, this);
      this._cookieAsset = null;
    }
    this.cookie = null;
  }, onEnable:function() {
    LightComponent._super.onEnable.call(this);
    this.light.enabled = true;
    if (this._cookieAsset && !this.cookie) {
      this.onCookieAssetSet();
    }
  }, onDisable:function() {
    LightComponent._super.onDisable.call(this);
    this.light.enabled = false;
  }});
  return {LightComponent:LightComponent, _lightProps:_props, _lightPropsDefault:_propsDefault};
}());
pc.extend(pc, function() {
  var LightComponentData = function() {
    var _props = pc._lightProps;
    var _propsDefault = pc._lightPropsDefault;
    var value;
    for (var i = 0;i < _props.length;i++) {
      value = _propsDefault[i];
      if (value && value.clone) {
        this[_props[i]] = value.clone();
      } else {
        this[_props[i]] = value;
      }
    }
  };
  LightComponentData = pc.inherits(LightComponentData, pc.ComponentData);
  return {LightComponentData:LightComponentData};
}());
pc.extend(pc, function() {
  var ScriptComponentSystem = function ScriptComponentSystem(app) {
    this.id = "script";
    this.app = app;
    app.systems.add(this.id, this);
    this.ComponentType = pc.ScriptComponent;
    this.DataType = pc.ScriptComponentData;
    this.schema = ["enabled"];
    this._components = [];
    this.on("beforeremove", this._onBeforeRemove, this);
    pc.ComponentSystem.on("initialize", this._onInitialize, this);
    pc.ComponentSystem.on("postInitialize", this._onPostInitialize, this);
    pc.ComponentSystem.on("update", this._onUpdate, this);
    pc.ComponentSystem.on("postUpdate", this._onPostUpdate, this);
  };
  ScriptComponentSystem = pc.inherits(ScriptComponentSystem, pc.ComponentSystem);
  pc.extend(ScriptComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    this._components.push(component);
    component.enabled = data.hasOwnProperty("enabled") ? !!data.enabled : true;
    if (data.hasOwnProperty("order") && data.hasOwnProperty("scripts")) {
      component._scriptsData = data.scripts;
      for (var i = 0;i < data.order.length;i++) {
        component.create(data.order[i], {enabled:data.scripts[data.order[i]].enabled, attributes:data.scripts[data.order[i]].attributes, preloading:true});
      }
    }
  }, cloneComponent:function(entity, clone) {
    var order = [];
    var scripts = {};
    for (var i = 0;i < entity.script._scripts.length;i++) {
      var scriptInstance = entity.script._scripts[i];
      var scriptName = scriptInstance.__scriptType.__name;
      order.push(scriptName);
      var attributes = {};
      for (var key in scriptInstance.__attributes) {
        attributes[key] = scriptInstance.__attributes[key];
      }
      scripts[scriptName] = {enabled:scriptInstance._enabled, attributes:attributes};
    }
    for (var key in entity.script._scriptsIndex) {
      var scriptData = entity.script._scriptsIndex[key];
      if (key.awayting) {
        order.splice(key.ind, 0, key);
      }
    }
    var data = {enabled:entity.script.enabled, order:order, scripts:scripts};
    return this.addComponent(clone, data);
  }, _callComponentMethod:function(name, dt) {
    for (var i = 0;i < this._components.length;i++) {
      if (!this._components[i].entity.enabled || !this._components[i].enabled) {
        continue;
      }
      this._components[i][name](dt);
    }
  }, _onInitialize:function() {
    for (var i = 0;i < this._components.length;i++) {
      this._components[i]._onInitializeAttributes();
    }
    this._callComponentMethod("_onInitialize");
  }, _onPostInitialize:function() {
    this._callComponentMethod("_onPostInitialize");
  }, _onUpdate:function(dt) {
    this._callComponentMethod("_onUpdate", dt);
  }, _onPostUpdate:function(dt) {
    this._callComponentMethod("_onPostUpdate", dt);
  }, _onBeforeRemove:function(entity, component) {
    var ind = this._components.indexOf(component);
    if (ind === -1) {
      return;
    }
    component._onBeforeRemove();
    this._components.splice(ind, 1);
  }});
  return {ScriptComponentSystem:ScriptComponentSystem};
}());
pc.extend(pc, function() {
  var ScriptComponent = function ScriptComponent(system, entity) {
    this._scripts = [];
    this._scriptsIndex = {};
    this._scriptsData = null;
    this._oldState = true;
    this.on("set_enabled", this._onSetEnabled, this);
  };
  ScriptComponent = pc.inherits(ScriptComponent, pc.Component);
  ScriptComponent.scriptMethods = {initialize:"initialize", postInitialize:"postInitialize", update:"update", postUpdate:"postUpdate", swap:"swap"};
  pc.extend(ScriptComponent.prototype, {onEnable:function() {
    ScriptComponent._super.onEnable.call(this);
    this._checkState();
  }, onDisable:function() {
    ScriptComponent._super.onDisable.call(this);
    this._checkState();
  }, onPostStateChange:function() {
    var script;
    for (var i = 0;i < this.scripts.length;i++) {
      script = this.scripts[i];
      if (script._initialized && !script._postInitialized) {
        script._postInitialized = true;
        if (script.postInitialize) {
          this._scriptMethod(script, ScriptComponent.scriptMethods.postInitialize);
        }
      }
    }
  }, _onSetEnabled:function(prop, old, value) {
    this._checkState();
  }, _checkState:function() {
    var state = this.enabled && this.entity.enabled;
    if (state === this._oldState) {
      return;
    }
    this._oldState = state;
    this.fire("enable");
    this.fire("state", this.enabled);
    var script;
    for (var i = 0, len = this.scripts.length;i < len;i++) {
      script = this.scripts[i];
      script.enabled = script._enabled;
      if (!script._initialized && script.enabled) {
        script._initialized = true;
        script.__initializeAttributes(true);
        if (script.initialize) {
          this._scriptMethod(script, ScriptComponent.scriptMethods.initialize);
        }
      }
    }
  }, _onBeforeRemove:function() {
    this.fire("remove");
    var destroyed = true;
    while (this.scripts.length > 0 && destroyed) {
      destroyed = this.destroy(this.scripts[0].__scriptType.__name);
    }
  }, _onInitializeAttributes:function() {
    for (var i = 0, len = this.scripts.length;i < len;i++) {
      this.scripts[i].__initializeAttributes();
    }
  }, _scriptMethod:function(script, method, arg) {
    try {
      script[method](arg);
    } catch (ex) {
      script.enabled = false;
      if (!script._callbacks || !script._callbacks.error) {
        console.warn('unhandled exception while calling "' + method + '" for "' + script.__scriptType.__name + '" script: ', ex);
        console.error(ex);
      }
      script.fire("error", ex, method);
      this.fire("error", script, ex, method);
    }
  }, _onInitialize:function() {
    var script, scripts = this._scripts;
    for (var i = 0, len = scripts.length;i < len;i++) {
      script = scripts[i];
      if (!script._initialized && script.enabled) {
        script._initialized = true;
        if (script.initialize) {
          this._scriptMethod(script, ScriptComponent.scriptMethods.initialize);
        }
      }
    }
  }, _onPostInitialize:function() {
    var script, scripts = this._scripts;
    for (var i = 0, len = scripts.length;i < len;i++) {
      script = scripts[i];
      if (!script._postInitialized && script.enabled) {
        script._postInitialized = true;
        if (script.postInitialize) {
          this._scriptMethod(script, ScriptComponent.scriptMethods.postInitialize);
        }
      }
    }
  }, _onUpdate:function(dt) {
    var script, scripts = this._scripts;
    for (var i = 0, len = scripts.length;i < len;i++) {
      script = scripts[i];
      if (script.update && script.enabled) {
        this._scriptMethod(script, ScriptComponent.scriptMethods.update, dt);
      }
    }
  }, _onPostUpdate:function(dt) {
    var script, scripts = this._scripts;
    for (var i = 0, len = scripts.length;i < len;i++) {
      script = scripts[i];
      if (script.postUpdate && script.enabled) {
        this._scriptMethod(script, ScriptComponent.scriptMethods.postUpdate, dt);
      }
    }
  }, has:function(name) {
    var scriptType = name;
    if (typeof scriptType === "string") {
      scriptType = this.system.app.scripts.get(scriptType);
    }
    return !!this._scriptsIndex[scriptType.__name];
  }, create:function(name, args) {
    var self = this;
    args = args || {};
    var scriptType = name;
    var scriptName = name;
    if (typeof scriptType === "string") {
      scriptType = this.system.app.scripts.get(scriptType);
    } else {
      if (scriptType) {
        scriptName = scriptType.__name;
      }
    }
    if (scriptType) {
      if (!this._scriptsIndex[scriptType.__name] || !this._scriptsIndex[scriptType.__name].instance) {
        var scriptInstance = new scriptType({app:this.system.app, entity:this.entity, enabled:args.hasOwnProperty("enabled") ? args.enabled : true, attributes:args.attributes || null});
        var ind = -1;
        if (typeof args.ind === "number" && args.ind !== -1 && this._scripts.length > args.ind) {
          ind = args.ind;
        }
        if (ind === -1) {
          this._scripts.push(scriptInstance);
        } else {
          this._scripts.splice(ind, 0, scriptInstance);
        }
        this._scriptsIndex[scriptType.__name] = {instance:scriptInstance, onSwap:function() {
          self.swap(scriptType.__name);
        }};
        this[scriptType.__name] = scriptInstance;
        if (!args.preloading) {
          scriptInstance.__initializeAttributes();
        }
        this.fire("create", scriptType.__name, scriptInstance);
        this.fire("create:" + scriptType.__name, scriptInstance);
        this.system.app.scripts.on("swap:" + scriptType.__name, this._scriptsIndex[scriptType.__name].onSwap);
        if (!args.preloading && this.enabled && scriptInstance.enabled && !scriptInstance._initialized) {
          scriptInstance._initialized = true;
          scriptInstance._postInitialized = true;
          if (scriptInstance.initialize) {
            this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.initialize);
          }
          if (scriptInstance.postInitialize) {
            this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.postInitialize);
          }
        }
        return scriptInstance;
      } else {
        console.warn("script '" + scriptName + "' is already added to entity '" + this.entity.name + "'");
      }
    } else {
      this._scriptsIndex[scriptName] = {awaiting:true, ind:this._scripts.length};
      console.warn("script '" + scriptName + "' is not found, awaiting it to be added to registry");
    }
    return null;
  }, destroy:function(name) {
    var scriptName = name;
    var scriptType = name;
    if (typeof scriptType === "string") {
      scriptType = this.system.app.scripts.get(scriptType);
      if (scriptType) {
        scriptName = scriptType.__name;
      }
    }
    var scriptData = this._scriptsIndex[scriptName];
    delete this._scriptsIndex[scriptName];
    if (!scriptData) {
      return false;
    }
    if (scriptData.instance) {
      var ind = this._scripts.indexOf(scriptData.instance);
      this._scripts.splice(ind, 1);
    }
    this.system.app.scripts.unbind("swap:" + scriptName, scriptData.onSwap);
    delete this._scriptsIndex[scriptName];
    delete this[scriptName];
    this.fire("destroy", scriptName, scriptData.instance || null);
    this.fire("destroy:" + scriptName, scriptData.instance || null);
    if (scriptData.instance) {
      scriptData.instance.fire("destroy");
    }
    return true;
  }, swap:function(script) {
    var scriptType = script;
    if (typeof scriptType === "string") {
      scriptType = this.system.app.scripts.get(scriptType);
    }
    var old = this._scriptsIndex[scriptType.__name];
    if (!old || !old.instance) {
      return false;
    }
    var scriptInstanceOld = old.instance;
    var ind = this._scripts.indexOf(scriptInstanceOld);
    var scriptInstance = new scriptType({app:this.system.app, entity:this.entity, enabled:scriptInstanceOld.enabled, attributes:scriptInstanceOld.__attributes});
    if (!scriptInstance.swap) {
      return false;
    }
    scriptInstance.__initializeAttributes();
    this._scripts[ind] = scriptInstance;
    this._scriptsIndex[scriptType.__name].instance = scriptInstance;
    this[scriptType.__name] = scriptInstance;
    this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.swap, scriptInstanceOld);
    this.fire("swap", scriptType.__name, scriptInstance);
    this.fire("swap:" + scriptType.__name, scriptInstance);
    return true;
  }, move:function(name, ind) {
    if (ind >= this._scripts.length) {
      return false;
    }
    var scriptName = name;
    if (typeof scriptName !== "string") {
      scriptName = name.__name;
    }
    var scriptData = this._scriptsIndex[scriptName];
    if (!scriptData || !scriptData.instance) {
      return false;
    }
    var indOld = this._scripts.indexOf(scriptData.instance);
    if (indOld === -1 || indOld === ind) {
      return false;
    }
    this._scripts.splice(ind, 0, this._scripts.splice(indOld, 1)[0]);
    this.fire("move", scriptName, scriptData.instance, ind, indOld);
    this.fire("move:" + scriptName, scriptData.instance, ind, indOld);
    return true;
  }});
  Object.defineProperty(ScriptComponent.prototype, "scripts", {get:function() {
    return this._scripts;
  }, set:function(value) {
    this._scriptsData = value;
    for (var key in value) {
      if (!value.hasOwnProperty(key)) {
        continue;
      }
      var script = this._scriptsIndex[key];
      if (script) {
        if (typeof value[key].enabled === "boolean") {
          script.enabled = !!value[key].enabled;
        }
        if (typeof value[key].attributes === "object") {
          for (var attr in value[key].attributes) {
            if (pc.createScript.reservedAttributes[attr]) {
              continue;
            }
            if (!script.__attributes.hasOwnProperty(attr)) {
              var scriptType = this.system.app.scripts.get(key);
              if (scriptType) {
                scriptType.attributes.add(attr, {});
              }
            }
            script[attr] = value[key].attributes[attr];
          }
        }
      } else {
        console.log(this.order);
      }
    }
  }});
  return {ScriptComponent:ScriptComponent};
}());
pc.extend(pc, function() {
  var ScriptComponentData = function() {
    this.enabled = true;
  };
  ScriptComponentData = pc.inherits(ScriptComponentData, pc.ComponentData);
  return {ScriptComponentData:ScriptComponentData};
}());
pc.extend(pc, function() {
  var INITIALIZE = "initialize";
  var POST_INITIALIZE = "postInitialize";
  var UPDATE = "update";
  var POST_UPDATE = "postUpdate";
  var FIXED_UPDATE = "fixedUpdate";
  var TOOLS_UPDATE = "toolsUpdate";
  var ON_ENABLE = "onEnable";
  var ON_DISABLE = "onDisable";
  var ScriptLegacyComponentSystem = function ScriptLegacyComponentSystem(app) {
    this.id = "script";
    this.description = "Allows the Entity to run JavaScript fragments to implement custom behavior.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.ScriptLegacyComponent;
    this.DataType = pc.ScriptLegacyComponentData;
    this.schema = ["enabled", "scripts", "instances", "runInTools"];
    this.preloading = false;
    this.instancesWithUpdate = [];
    this.instancesWithFixedUpdate = [];
    this.instancesWithPostUpdate = [];
    this.instancesWithToolsUpdate = [];
    this.on("beforeremove", this.onBeforeRemove, this);
    pc.ComponentSystem.on(INITIALIZE, this.onInitialize, this);
    pc.ComponentSystem.on(POST_INITIALIZE, this.onPostInitialize, this);
    pc.ComponentSystem.on(UPDATE, this.onUpdate, this);
    pc.ComponentSystem.on(FIXED_UPDATE, this.onFixedUpdate, this);
    pc.ComponentSystem.on(POST_UPDATE, this.onPostUpdate, this);
    pc.ComponentSystem.on(TOOLS_UPDATE, this.onToolsUpdate, this);
  };
  ScriptLegacyComponentSystem = pc.inherits(ScriptLegacyComponentSystem, pc.ComponentSystem);
  pc.extend(ScriptLegacyComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    properties = ["runInTools", "enabled", "scripts"];
    if (data.scripts && data.scripts.length) {
      data.scripts.forEach(function(script) {
        if (script.attributes && pc.type(script.attributes) === "array") {
          var dict = {};
          for (var i = 0;i < script.attributes.length;i++) {
            dict[script.attributes[i].name] = script.attributes[i];
          }
          script.attributes = dict;
        }
      });
    }
    ScriptLegacyComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, cloneComponent:function(entity, clone) {
    var src = this.dataStore[entity._guid];
    var data = {runInTools:src.data.runInTools, scripts:[], enabled:src.data.enabled};
    var scripts = src.data.scripts;
    for (var i = 0, len = scripts.length;i < len;i++) {
      var attributes = scripts[i].attributes;
      if (attributes) {
        delete scripts[i].attributes;
      }
      data.scripts.push(pc.extend({}, scripts[i]));
      if (attributes) {
        data.scripts[i].attributes = this._cloneAttributes(attributes);
        scripts[i].attributes = attributes;
      }
    }
    return this.addComponent(clone, data);
  }, onBeforeRemove:function(entity, component) {
    if (component.enabled) {
      this._disableScriptComponent(component);
    }
    this._destroyScriptComponent(component);
  }, onInitialize:function(root) {
    this._registerInstances(root);
    if (root.enabled) {
      if (root.script && root.script.enabled) {
        this._initializeScriptComponent(root.script);
      }
      var children = root._children;
      var i, len = children.length;
      for (i = 0;i < len;i++) {
        if (children[i] instanceof pc.Entity) {
          this.onInitialize(children[i]);
        }
      }
    }
  }, onPostInitialize:function(root) {
    if (root.enabled) {
      if (root.script && root.script.enabled) {
        this._postInitializeScriptComponent(root.script);
      }
      var children = root._children;
      var i, len = children.length;
      for (i = 0;i < len;i++) {
        if (children[i] instanceof pc.Entity) {
          this.onPostInitialize(children[i]);
        }
      }
    }
  }, _callInstancesMethod:function(script, method) {
    var instances = script.data.instances;
    for (var name in instances) {
      if (instances.hasOwnProperty(name)) {
        var instance = instances[name].instance;
        if (instance[method]) {
          instance[method].call(instance);
        }
      }
    }
  }, _initializeScriptComponent:function(script) {
    this._callInstancesMethod(script, INITIALIZE);
    script.data.initialized = true;
    if (script.enabled && script.entity.enabled) {
      this._enableScriptComponent(script);
    }
  }, _enableScriptComponent:function(script) {
    this._callInstancesMethod(script, ON_ENABLE);
  }, _disableScriptComponent:function(script) {
    this._callInstancesMethod(script, ON_DISABLE);
  }, _destroyScriptComponent:function(script) {
    var index;
    var instances = script.data.instances;
    for (var name in instances) {
      if (instances.hasOwnProperty(name)) {
        var instance = instances[name].instance;
        if (instance.destroy) {
          instance.destroy();
        }
        if (instance.update) {
          index = this.instancesWithUpdate.indexOf(instance);
          if (index >= 0) {
            this.instancesWithUpdate.splice(index, 1);
          }
        }
        if (instance.fixedUpdate) {
          index = this.instancesWithFixedUpdate.indexOf(instance);
          if (index >= 0) {
            this.instancesWithFixedUpdate.splice(index, 1);
          }
        }
        if (instance.postUpdate) {
          index = this.instancesWithPostUpdate.indexOf(instance);
          if (index >= 0) {
            this.instancesWithPostUpdate.splice(index, 1);
          }
        }
        if (instance.toolsUpdate) {
          index = this.instancesWithToolsUpdate.indexOf(instance);
          if (index >= 0) {
            this.instancesWithToolsUpdate.splice(index, 1);
          }
        }
        if (script.instances[name].instance === script[name]) {
          delete script[name];
        }
        delete script.instances[name];
      }
    }
  }, _postInitializeScriptComponent:function(script) {
    this._callInstancesMethod(script, POST_INITIALIZE);
    script.data.postInitialized = true;
  }, _updateInstances:function(method, updateList, dt) {
    var item;
    for (var i = 0, len = updateList.length;i < len;i++) {
      item = updateList[i];
      if (item && item.entity && item.entity.enabled && item.entity.script.enabled) {
        item[method].call(item, dt);
      }
    }
  }, onUpdate:function(dt) {
    this._updateInstances(UPDATE, this.instancesWithUpdate, dt);
  }, onFixedUpdate:function(dt) {
    this._updateInstances(FIXED_UPDATE, this.instancesWithFixedUpdate, dt);
  }, onPostUpdate:function(dt) {
    this._updateInstances(POST_UPDATE, this.instancesWithPostUpdate, dt);
  }, onToolsUpdate:function(dt) {
    this._updateInstances(TOOLS_UPDATE, this.instancesWithToolsUpdate, dt);
  }, broadcast:function(name, functionName) {
    console.warn("DEPRECATED: ScriptLegacyComponentSystem.broadcast() is deprecated and will be removed soon. Please use: http://developer.playcanvas.com/user-manual/scripting/communication/");
    var args = pc.makeArray(arguments).slice(2);
    var id, data, fn;
    var dataStore = this.store;
    for (id in dataStore) {
      if (dataStore.hasOwnProperty(id)) {
        data = dataStore[id].data;
        if (data.instances[name]) {
          fn = data.instances[name].instance[functionName];
          if (fn) {
            fn.apply(data.instances[name].instance, args);
          }
        }
      }
    }
  }, _preRegisterInstance:function(entity, url, name, instance) {
    if (entity.script) {
      entity.script.data._instances = entity.script.data._instances || {};
      if (entity.script.data._instances[name]) {
        throw Error(pc.string.format("Script name collision '{0}'. Scripts from '{1}' and '{2}' {{3}}", name, url, entity.script.data._instances[name].url, entity._guid));
      }
      entity.script.data._instances[name] = {url:url, name:name, instance:instance};
    }
  }, _registerInstances:function(entity) {
    var preRegistered, instance, instanceName;
    if (entity.script) {
      if (entity.script.data._instances) {
        entity.script.instances = entity.script.data._instances;
        for (instanceName in entity.script.instances) {
          preRegistered = entity.script.instances[instanceName];
          instance = preRegistered.instance;
          pc.events.attach(instance);
          if (instance.update) {
            this.instancesWithUpdate.push(instance);
          }
          if (instance.fixedUpdate) {
            this.instancesWithFixedUpdate.push(instance);
          }
          if (instance.postUpdate) {
            this.instancesWithPostUpdate.push(instance);
          }
          if (instance.toolsUpdate) {
            this.instancesWithToolsUpdate.push(instance);
          }
          if (entity.script.scripts) {
            this._createAccessors(entity, preRegistered);
          }
          if (entity.script[instanceName]) {
            throw Error(pc.string.format("Script with name '{0}' is already attached to Script Component", instanceName));
          } else {
            entity.script[instanceName] = instance;
          }
        }
        delete entity.script.data._instances;
      }
    }
    var children = entity._children;
    var i, len = children.length;
    for (i = 0;i < len;i++) {
      if (children[i] instanceof pc.Entity) {
        this._registerInstances(children[i]);
      }
    }
  }, _cloneAttributes:function(attributes) {
    var result = {};
    for (var key in attributes) {
      if (!attributes.hasOwnProperty(key)) {
        continue;
      }
      if (attributes[key].type !== "entity") {
        result[key] = pc.extend({}, attributes[key]);
      } else {
        var val = attributes[key].value;
        delete attributes[key].value;
        result[key] = pc.extend({}, attributes[key]);
        result[key].value = val;
        attributes[key].value = val;
      }
    }
    return result;
  }, _createAccessors:function(entity, instance) {
    var self = this;
    var i;
    var len = entity.script.scripts.length;
    var url = instance.url;
    for (i = 0;i < len;i++) {
      var script = entity.script.scripts[i];
      if (script.url === url) {
        var attributes = script.attributes;
        if (script.name && attributes) {
          for (var key in attributes) {
            if (attributes.hasOwnProperty(key)) {
              self._createAccessor(attributes[key], instance);
            }
          }
          entity.script.data.attributes[script.name] = self._cloneAttributes(attributes);
        }
        break;
      }
    }
  }, _createAccessor:function(attribute, instance) {
    var self = this;
    attribute = {name:attribute.name, value:attribute.value, type:attribute.type};
    self._convertAttributeValue(attribute);
    Object.defineProperty(instance.instance, attribute.name, {get:function() {
      return attribute.value;
    }, set:function(value) {
      var oldValue = attribute.value;
      attribute.value = value;
      self._convertAttributeValue(attribute);
      instance.instance.fire("set", attribute.name, oldValue, attribute.value);
    }, configurable:true});
  }, _updateAccessors:function(entity, instance) {
    var self = this;
    var i;
    var len = entity.script.scripts.length;
    var key;
    var url = instance.url;
    var scriptComponent, script, name, attributes;
    var previousAttributes;
    var oldAttribute;
    for (i = 0;i < len;i++) {
      scriptComponent = entity.script;
      script = scriptComponent.scripts[i];
      if (script.url === url) {
        name = script.name;
        attributes = script.attributes;
        if (name) {
          if (attributes) {
            for (key in attributes) {
              if (attributes.hasOwnProperty(key)) {
                self._createAccessor(attributes[key], instance);
              }
            }
          }
          previousAttributes = scriptComponent.data.attributes[name];
          if (previousAttributes) {
            for (key in previousAttributes) {
              oldAttribute = previousAttributes[key];
              if (!(key in attributes)) {
                delete instance.instance[oldAttribute.name];
              } else {
                if (attributes[key].value !== oldAttribute.value) {
                  if (instance.instance.onAttributeChanged) {
                    instance.instance.onAttributeChanged(oldAttribute.name, oldAttribute.value, attributes[key].value);
                  }
                }
              }
            }
          }
          if (attributes) {
            scriptComponent.data.attributes[name] = self._cloneAttributes(attributes);
          } else {
            delete scriptComponent.data.attributes[name];
          }
        }
        break;
      }
    }
  }, _convertAttributeValue:function(attribute) {
    if (attribute.type === "rgb" || attribute.type === "rgba") {
      if (pc.type(attribute.value) === "array") {
        attribute.value = attribute.value.length === 3 ? new pc.Color(attribute.value[0], attribute.value[1], attribute.value[2]) : new pc.Color(attribute.value[0], attribute.value[1], attribute.value[2], attribute.value[3]);
      }
    } else {
      if (attribute.type === "vec2") {
        if (pc.type(attribute.value) === "array") {
          attribute.value = new pc.Vec2(attribute.value[0], attribute.value[1]);
        }
      } else {
        if (attribute.type === "vec3" || attribute.type === "vector") {
          if (pc.type(attribute.value) === "array") {
            attribute.value = new pc.Vec3(attribute.value[0], attribute.value[1], attribute.value[2]);
          }
        } else {
          if (attribute.type === "vec4") {
            if (pc.type(attribute.value) === "array") {
              attribute.value = new pc.Vec4(attribute.value[0], attribute.value[1], attribute.value[2], attribute.value[3]);
            }
          } else {
            if (attribute.type === "entity") {
              if (attribute.value !== null && typeof attribute.value === "string") {
                attribute.value = this.app.root.findByGuid(attribute.value);
              }
            } else {
              if (attribute.type === "curve" || attribute.type === "colorcurve") {
                var curveType = attribute.value.keys[0] instanceof Array ? pc.CurveSet : pc.Curve;
                attribute.value = new curveType(attribute.value.keys);
                attribute.value.type = attribute.value.type;
              }
            }
          }
        }
      }
    }
  }});
  return {ScriptLegacyComponentSystem:ScriptLegacyComponentSystem};
}());
pc.extend(pc, function() {
  var ScriptLegacyComponent = function ScriptLegacyComponent(system, entity) {
    this.on("set_scripts", this.onSetScripts, this);
  };
  ScriptLegacyComponent = pc.inherits(ScriptLegacyComponent, pc.Component);
  pc.extend(ScriptLegacyComponent.prototype, {send:function(name, functionName) {
    console.warn("DEPRECATED: ScriptLegacyComponent.send() is deprecated and will be removed soon. Please use: http://developer.playcanvas.com/user-manual/scripting/communication/");
    var args = pc.makeArray(arguments).slice(2);
    var instances = this.entity.script.instances;
    var fn;
    if (instances && instances[name]) {
      fn = instances[name].instance[functionName];
      if (fn) {
        return fn.apply(instances[name].instance, args);
      }
    }
  }, onEnable:function() {
    ScriptLegacyComponent._super.onEnable.call(this);
    if (this.data.areScriptsLoaded && !this.system.preloading) {
      if (!this.data.initialized) {
        this.system._initializeScriptComponent(this);
      } else {
        this.system._enableScriptComponent(this);
      }
      if (!this.data.postInitialized) {
        this.system._postInitializeScriptComponent(this);
      }
    }
  }, onDisable:function() {
    ScriptLegacyComponent._super.onDisable.call(this);
    this.system._disableScriptComponent(this);
  }, onSetScripts:function(name, oldValue, newValue) {
    if (!this.system._inTools || this.runInTools) {
      if (this._updateScriptAttributes(oldValue, newValue)) {
        return;
      }
      if (this.enabled) {
        this.system._disableScriptComponent(this);
      }
      this.system._destroyScriptComponent(this);
      this.data.areScriptsLoaded = false;
      var scripts = newValue;
      var urls = scripts.map(function(s) {
        return s.url;
      });
      if (this._loadFromCache(urls)) {
        return;
      }
      this._loadScripts(urls);
    }
  }, _updateScriptAttributes:function(oldValue, newValue) {
    var onlyUpdateAttributes = true;
    if (oldValue.length !== newValue.length) {
      onlyUpdateAttributes = false;
    } else {
      var i, len = newValue.length;
      for (i = 0;i < len;i++) {
        if (oldValue[i].url !== newValue[i].url) {
          onlyUpdateAttributes = false;
          break;
        }
      }
    }
    if (onlyUpdateAttributes) {
      for (var key in this.instances) {
        if (this.instances.hasOwnProperty(key)) {
          this.system._updateAccessors(this.entity, this.instances[key]);
        }
      }
    }
    return onlyUpdateAttributes;
  }, _loadFromCache:function(urls) {
    var i, len;
    var cached = [];
    var prefix = this.system.app._scriptPrefix || "";
    var regex = /^http(s)?:\/\//i;
    for (i = 0, len = urls.length;i < len;i++) {
      var url = urls[i];
      if (!regex.test(url)) {
        url = pc.path.join(prefix, url);
      }
      var type = this.system.app.loader.getFromCache(url, "script");
      if (!type) {
        return false;
      } else {
        cached.push(type);
      }
    }
    for (i = 0, len = cached.length;i < len;i++) {
      var ScriptType = cached[i];
      if (ScriptType === true) {
        continue;
      }
      if (ScriptType && this.entity.script) {
        if (!this.entity.script.instances[ScriptType._pcScriptName]) {
          var instance = new ScriptType(this.entity);
          this.system._preRegisterInstance(this.entity, urls[i], ScriptType._pcScriptName, instance);
        }
      }
    }
    if (this.data) {
      this.data.areScriptsLoaded = true;
    }
    if (!this.system.preloading) {
      this.system.onInitialize(this.entity);
      this.system.onPostInitialize(this.entity);
    }
    return true;
  }, _loadScripts:function(urls) {
    var count = urls.length;
    var prefix = this.system.app._scriptPrefix || "";
    urls.forEach(function(url) {
      var _url = null;
      var _unprefixed = null;
      if (url.toLowerCase().startsWith("http://") || url.toLowerCase().startsWith("https://")) {
        _unprefixed = url;
        _url = url;
      } else {
        _unprefixed = url;
        _url = pc.path.join(prefix, url);
      }
      this.system.app.loader.load(_url, "script", function(err, ScriptType) {
        count--;
        if (!err) {
          if (ScriptType && this.entity.script) {
            if (!this.entity.script.instances[ScriptType._pcScriptName]) {
              var instance = new ScriptType(this.entity);
              this.system._preRegisterInstance(this.entity, _unprefixed, ScriptType._pcScriptName, instance);
            }
          }
        } else {
          console.error(err);
        }
        if (count === 0) {
          this.data.areScriptsLoaded = true;
          if (!this.system.preloading) {
            this.system.onInitialize(this.entity);
            this.system.onPostInitialize(this.entity);
          }
        }
      }.bind(this));
    }.bind(this));
  }});
  return {ScriptLegacyComponent:ScriptLegacyComponent};
}());
pc.extend(pc, function() {
  var ScriptLegacyComponentData = function() {
    this.scripts = [];
    this.enabled = true;
    this.instances = {};
    this._instances = {};
    this.runInTools = false;
    this.attributes = {};
    this.initialized = false;
    this.postInitialized = false;
    this.areScriptsLoaded = false;
  };
  ScriptLegacyComponentData = pc.inherits(ScriptLegacyComponentData, pc.ComponentData);
  return {ScriptLegacyComponentData:ScriptLegacyComponentData};
}());
pc.extend(pc, {DISTANCE_LINEAR:"linear", DISTANCE_INVERSE:"inverse", DISTANCE_EXPONENTIAL:"exponential"});
pc.extend(pc, function() {
  var instanceOptions = {volume:0, pitch:0, loop:false, startTime:0, duration:0, position:new pc.Vec3, maxDistance:0, refDistance:0, rollOffFactor:0, distanceModel:0, onPlay:null, onPause:null, onResume:null, onStop:null, onEnd:null};
  var SoundSlot = function(component, name, options) {
    options = options || {};
    this._component = component;
    this._assets = component.system.app.assets;
    this._manager = component.system.manager;
    this._name = name || "Untitled";
    this._volume = options.volume !== undefined ? pc.math.clamp(Number(options.volume) || 0, 0, 1) : 1;
    this._pitch = options.pitch !== undefined ? Math.max(.01, Number(options.pitch) || 0) : 1;
    this._loop = !!(options.loop !== undefined ? options.loop : false);
    this._duration = options.duration > 0 ? options.duration : null;
    this._startTime = Math.max(0, Number(options.startTime) || 0);
    this._overlap = !!options.overlap;
    this._autoPlay = !!options.autoPlay;
    this._firstNode = null;
    this._lastNode = null;
    this._asset = options.asset;
    if (this._asset instanceof pc.Asset) {
      this._asset = this._asset.id;
    }
    this._onInstancePlayHandler = this._onInstancePlay.bind(this);
    this._onInstancePauseHandler = this._onInstancePause.bind(this);
    this._onInstanceResumeHandler = this._onInstanceResume.bind(this);
    this._onInstanceStopHandler = this._onInstanceStop.bind(this);
    this._onInstanceEndHandler = this._onInstanceEnd.bind(this);
    this.instances = [];
    pc.events.attach(this);
  };
  SoundSlot.prototype = {play:function() {
    if (!this.overlap && (this.isPlaying || this.isPaused)) {
      this.stop();
    }
    var instance = this._createInstance();
    this.instances.push(instance);
    if (!this.isLoaded) {
      var onLoad = function(sound) {
        instance.sound = sound;
        if (instance._playWhenLoaded) {
          instance.play();
        }
      };
      this.off("load", onLoad);
      this.once("load", onLoad);
      this.load();
    } else {
      instance.play();
    }
    return instance;
  }, pause:function() {
    var paused = false;
    var instances = this.instances;
    for (var i = 0, len = instances.length;i < len;i++) {
      if (instances[i].pause()) {
        paused = true;
      }
    }
    return paused;
  }, resume:function() {
    var resumed = false;
    var instances = this.instances;
    for (var i = 0, len = instances.length;i < len;i++) {
      if (instances[i].resume()) {
        resumed = true;
      }
    }
    return resumed;
  }, stop:function() {
    var stopped = false;
    var instances = this.instances;
    for (var i = 0, len = instances.length;i < len;i++) {
      if (instances[i].stop()) {
        stopped = true;
      }
    }
    instances.length = 0;
    return stopped;
  }, load:function() {
    if (!this._hasAsset()) {
      return;
    }
    var asset = this._assets.get(this._asset);
    if (!asset) {
      this._assets.off("add:" + this._asset, this._onAssetAdd, this);
      this._assets.once("add:" + this._asset, this._onAssetAdd, this);
      return;
    }
    asset.off("remove", this._onAssetRemoved, this);
    asset.on("remove", this._onAssetRemoved, this);
    if (!asset.resource) {
      asset.off("load", this._onAssetLoad, this);
      asset.once("load", this._onAssetLoad, this);
      this._assets.load(asset);
      return;
    }
    this.fire("load", asset.resource);
  }, setExternalNodes:function(firstNode, lastNode) {
    if (!firstNode) {
      console.error("The firstNode must have a valid AudioNode");
      return;
    }
    if (!lastNode) {
      lastNode = firstNode;
    }
    this._firstNode = firstNode;
    this._lastNode = lastNode;
    if (!this._overlap) {
      var instances = this.instances;
      for (var i = 0, len = instances.length;i < len;i++) {
        instances[i].setExternalNodes(firstNode, lastNode);
      }
    }
  }, clearExternalNodes:function() {
    this._firstNode = null;
    this._lastNode = null;
    if (!this._overlap) {
      var instances = this.instances;
      for (var i = 0, len = instances.length;i < len;i++) {
        instances[i].clearExternalNodes();
      }
    }
  }, getExternalNodes:function() {
    return [this._firstNode, this._lastNode];
  }, _hasAsset:function() {
    return this._asset != null;
  }, _createInstance:function() {
    var instance = null;
    var component = this._component;
    var sound = null;
    if (this._hasAsset()) {
      var asset = this._assets.get(this._asset);
      if (asset) {
        sound = asset.resource;
      }
    }
    var data = instanceOptions;
    data.volume = this._volume * component.volume;
    data.pitch = this._pitch * component.pitch;
    data.loop = this._loop;
    data.startTime = this._startTime;
    data.duration = this._duration;
    data.onPlay = this._onInstancePlayHandler;
    data.onPause = this._onInstancePauseHandler;
    data.onResume = this._onInstanceResumeHandler;
    data.onStop = this._onInstanceStopHandler;
    data.onEnd = this._onInstanceEndHandler;
    if (component.positional) {
      data.position.copy(component.entity.getPosition());
      data.maxDistance = component.maxDistance;
      data.refDistance = component.refDistance;
      data.rollOffFactor = component.rollOffFactor;
      data.distanceModel = component.distanceModel;
      instance = new pc.SoundInstance3d(this._manager, sound, data);
    } else {
      instance = new pc.SoundInstance(this._manager, sound, data);
    }
    if (this._firstNode) {
      instance.setExternalNodes(this._firstNode, this._lastNode);
    }
    return instance;
  }, _onInstancePlay:function(instance) {
    this.fire("play", instance);
    this._component.fire("play", this, instance);
  }, _onInstancePause:function(instance) {
    this.fire("pause", instance);
    this._component.fire("pause", this, instance);
  }, _onInstanceResume:function(instance) {
    this.fire("resume", instance);
    this._component.fire("resume", this, instance);
  }, _onInstanceStop:function(instance) {
    this.fire("stop", instance);
    this._component.fire("stop", this, instance);
  }, _onInstanceEnd:function(instance) {
    var idx = this.instances.indexOf(instance);
    if (idx !== -1) {
      this.instances.splice(idx, 1);
    }
    this.fire("end", instance);
    this._component.fire("end", this, instance);
  }, _onAssetAdd:function(asset) {
    this.load();
  }, _onAssetLoad:function(asset) {
    this.load();
  }, _onAssetRemoved:function(asset) {
    asset.off("remove", this._onAssetRemoved, this);
    this._assets.off("add:" + asset.id, this._onAssetAdd, this);
    this.stop();
  }, updatePosition:function(position) {
    var instances = this.instances;
    for (var i = 0, len = instances.length;i < len;i++) {
      instances[i].position = position;
    }
  }};
  Object.defineProperty(SoundSlot.prototype, "name", {get:function() {
    return this._name;
  }, set:function(value) {
    var old = this._name;
    this._name = value;
  }});
  Object.defineProperty(SoundSlot.prototype, "volume", {get:function() {
    return this._volume;
  }, set:function(value) {
    var old = this._volume;
    this._volume = pc.math.clamp(Number(value) || 0, 0, 1);
    if (!this._overlap) {
      var instances = this.instances;
      for (var i = 0, len = instances.length;i < len;i++) {
        instances[i].volume = this._volume * this._component.volume;
      }
    }
  }});
  Object.defineProperty(SoundSlot.prototype, "pitch", {get:function() {
    return this._pitch;
  }, set:function(value) {
    var old = this._pitch;
    this._pitch = Math.max(Number(value) || 0, .01);
    if (!this._overlap) {
      var instances = this.instances;
      for (var i = 0, len = instances.length;i < len;i++) {
        instances[i].pitch = this.pitch * this._component.pitch;
      }
    }
  }});
  Object.defineProperty(SoundSlot.prototype, "loop", {get:function() {
    return this._loop;
  }, set:function(value) {
    var old = this._loop;
    this._loop = !!value;
    var instances = this.instances;
    for (var i = 0, len = instances.length;i < len;i++) {
      instances[i].loop = this._loop;
    }
  }});
  Object.defineProperty(SoundSlot.prototype, "autoPlay", {get:function() {
    return this._autoPlay;
  }, set:function(value) {
    var old = this._autoPlay;
    this._autoPlay = !!value;
  }});
  Object.defineProperty(SoundSlot.prototype, "overlap", {get:function() {
    return this._overlap;
  }, set:function(value) {
    var old = this._overlap;
    this._overlap = !!value;
  }});
  Object.defineProperty(SoundSlot.prototype, "startTime", {get:function() {
    return this._startTime;
  }, set:function(value) {
    var old = this._startTime;
    this._startTime = Math.max(0, Number(value) || 0);
    if (!this._overlap) {
      var instances = this.instances;
      for (var i = 0, len = instances.length;i < len;i++) {
        instances[i].startTime = this._startTime;
      }
    }
  }});
  Object.defineProperty(SoundSlot.prototype, "duration", {get:function() {
    var assetDuration = 0;
    if (this._hasAsset()) {
      var asset = this._assets.get(this._asset);
      assetDuration = asset.resource ? asset.resource.duration : 0;
    }
    if (this._duration != null) {
      return this._duration % (assetDuration || 1);
    } else {
      return assetDuration;
    }
  }, set:function(value) {
    var old = this._duration;
    this._duration = Math.max(0, Number(value) || 0) || null;
    if (!this._overlap) {
      var instances = this.instances;
      for (var i = 0, len = instances.length;i < len;i++) {
        instances[i].duration = this._duration;
      }
    }
  }});
  Object.defineProperty(SoundSlot.prototype, "asset", {get:function() {
    return this._asset;
  }, set:function(value) {
    var old = this._asset;
    if (old) {
      this._assets.off("add:" + old, this._onAssetAdd, this);
      var oldAsset = this._assets.get(old);
      if (oldAsset) {
        oldAsset.off("remove", this._onAssetRemoved, this);
      }
    }
    this._asset = value;
    if (this._asset instanceof pc.Asset) {
      this._asset = this._asset.id;
    }
    if (this._hasAsset() && this._component.enabled && this._component.entity.enabled) {
      this.load();
    }
  }});
  Object.defineProperty(SoundSlot.prototype, "isLoaded", {get:function() {
    if (this._hasAsset()) {
      var asset = this._assets.get(this._asset);
      if (asset) {
        return !!asset.resource;
      }
    }
    return false;
  }});
  Object.defineProperty(SoundSlot.prototype, "isPlaying", {get:function() {
    var instances = this.instances;
    for (var i = 0, len = instances.length;i < len;i++) {
      if (instances[i].isPlaying) {
        return true;
      }
    }
    return false;
  }});
  Object.defineProperty(SoundSlot.prototype, "isPaused", {get:function() {
    var instances = this.instances;
    var len = instances.length;
    if (len === 0) {
      return false;
    }
    for (var i = 0;i < len;i++) {
      if (!instances[i].isPaused) {
        return false;
      }
    }
    return true;
  }});
  Object.defineProperty(SoundSlot.prototype, "isStopped", {get:function() {
    var instances = this.instances;
    for (var i = 0, len = instances.length;i < len;i++) {
      if (!instances[i].isStopped) {
        return false;
      }
    }
    return true;
  }});
  return {SoundSlot:SoundSlot};
}());
pc.extend(pc, function() {
  var SoundComponentSystem = function(app, manager) {
    this.id = "sound";
    this.description = "Allows an Entity to play sounds";
    app.systems.add(this.id, this);
    this.ComponentType = pc.SoundComponent;
    this.DataType = pc.SoundComponentData;
    this.schema = ["enabled", "volume", "pitch", "positional", "refDistance", "maxDistance", "rollOffFactor", "distanceModel", "slots"];
    this.manager = manager;
    pc.ComponentSystem.on("update", this.onUpdate, this);
    this.on("beforeremove", this.onBeforeRemove, this);
  };
  SoundComponentSystem = pc.inherits(SoundComponentSystem, pc.ComponentSystem);
  pc.extend(SoundComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    properties = ["volume", "pitch", "positional", "refDistance", "maxDistance", "rollOffFactor", "distanceModel", "slots", "enabled"];
    SoundComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, cloneComponent:function(entity, clone) {
    var oldData = entity.sound.data;
    var newData = {};
    for (var key in oldData) {
      if (oldData.hasOwnProperty(key)) {
        newData[key] = oldData[key];
      }
    }
    newData.slots = {};
    for (var key in oldData.slots) {
      var oldSlot = oldData.slots[key];
      if (oldSlot instanceof pc.SoundSlot) {
        newData.slots[key] = {name:oldSlot.name, volume:oldSlot.volume, pitch:oldSlot.pitch, loop:oldSlot.loop, duration:oldSlot.duration, startTime:oldSlot.startTime, overlap:oldSlot.overlap, autoPlay:oldSlot.autoPlay, asset:oldSlot.asset};
      } else {
        newData.slots[key] = oldSlot;
      }
    }
    newData.playingBeforeDisable = {};
    return this.addComponent(clone, newData);
  }, onUpdate:function(dt) {
    var store = this.store;
    for (var id in store) {
      if (store.hasOwnProperty(id)) {
        var item = store[id];
        var entity = item.entity;
        var componentData = item.data;
        if (componentData.enabled && entity.enabled && componentData.positional) {
          var position = entity.getPosition();
          var slots = componentData.slots;
          for (var key in slots) {
            slots[key].updatePosition(position);
          }
        }
      }
    }
  }, onBeforeRemove:function(entity, component) {
    var slots = component.slots;
    for (var key in slots) {
      if (!slots[key].overlap) {
        slots[key].stop();
      }
    }
  }});
  Object.defineProperty(SoundComponentSystem.prototype, "volume", {get:function() {
    return this.manager.volume;
  }, set:function(volume) {
    this.manager.volume = volume;
  }});
  Object.defineProperty(SoundComponentSystem.prototype, "context", {get:function() {
    if (!pc.SoundManager.hasAudioContext()) {
      console.warn("WARNING: Audio context is not supported on this browser");
      return null;
    }
    return this.manager.context;
  }});
  return {SoundComponentSystem:SoundComponentSystem};
}());
pc.extend(pc, function() {
  var SoundComponent = function(system, entity) {
    this.on("set_slots", this.onSetSlots, this);
    this.on("set_volume", this.onSetVolume, this);
    this.on("set_pitch", this.onSetPitch, this);
    this.on("set_refDistance", this.onSetRefDistance, this);
    this.on("set_maxDistance", this.onSetMaxDistance, this);
    this.on("set_rollOffFactor", this.onSetRollOffFactor, this);
    this.on("set_distanceModel", this.onSetDistanceModel, this);
    this.on("set_positional", this.onSetPositional, this);
  };
  SoundComponent = pc.inherits(SoundComponent, pc.Component);
  pc.extend(SoundComponent.prototype, {onSetSlots:function(name, oldValue, newValue) {
    if (oldValue) {
      for (var key in oldValue) {
        oldValue[key].stop();
      }
    }
    var slots = {};
    for (var key in newValue) {
      if (!(newValue[key] instanceof pc.SoundSlot)) {
        if (newValue[key].name) {
          slots[newValue[key].name] = new pc.SoundSlot(this, newValue[key].name, newValue[key]);
        }
      } else {
        slots[newValue[key].name] = newValue[key];
      }
    }
    this.data.slots = slots;
    if (this.enabled && this.entity.enabled) {
      this.onEnable();
    }
  }, onSetVolume:function(name, oldValue, newValue) {
    var slots = this.data.slots;
    for (var key in slots) {
      var slot = slots[key];
      if (!slot.overlap) {
        var instances = slot.instances;
        for (var i = 0, len = instances.length;i < len;i++) {
          instances[i].volume = slot.volume * newValue;
        }
      }
    }
  }, onSetPitch:function(name, oldValue, newValue) {
    var slots = this.data.slots;
    for (var key in slots) {
      var slot = slots[key];
      if (!slot.overlap) {
        var instances = slot.instances;
        for (var i = 0, len = instances.length;i < len;i++) {
          instances[i].pitch = slot.pitch * newValue;
        }
      }
    }
  }, onSetRefDistance:function(name, oldValue, newValue) {
    var slots = this.data.slots;
    for (var key in slots) {
      var slot = slots[key];
      if (!slot.overlap) {
        var instances = slot.instances;
        for (var i = 0, len = instances.length;i < len;i++) {
          instances[i].refDistance = newValue;
        }
      }
    }
  }, onSetMaxDistance:function(name, oldValue, newValue) {
    var slots = this.data.slots;
    for (var key in slots) {
      var slot = slots[key];
      if (!slot.overlap) {
        var instances = slot.instances;
        for (var i = 0, len = instances.length;i < len;i++) {
          instances[i].maxDistance = newValue;
        }
      }
    }
  }, onSetRollOffFactor:function(name, oldValue, newValue) {
    var slots = this.data.slots;
    for (var key in slots) {
      var slot = slots[key];
      if (!slot.overlap) {
        var instances = slot.instances;
        for (var i = 0, len = instances.length;i < len;i++) {
          instances[i].rollOffFactor = newValue;
        }
      }
    }
  }, onSetDistanceModel:function(name, oldValue, newValue) {
    var slots = this.data.slots;
    for (var key in slots) {
      var slot = slots[key];
      if (!slot.overlap) {
        var instances = slot.instances;
        for (var i = 0, len = instances.length;i < len;i++) {
          instances[i].distanceModel = newValue;
        }
      }
    }
  }, onSetPositional:function(name, oldValue, newValue) {
    var slots = this.data.slots;
    for (var key in slots) {
      var slot = slots[key];
      if (!slot.overlap) {
        var instances = slot.instances;
        for (var i = 0, len = instances.length;i < len;i++) {
          var isPlaying = instances[i].isPlaying || instances[i].isSuspended;
          var currentTime = instances[i].currentTime;
          if (isPlaying) {
            instances[i].stop();
          }
          instances[i] = slot._createInstance();
          if (isPlaying) {
            instances[i].play();
            instances[i].currentTime = currentTime;
          }
        }
      }
    }
  }, onEnable:function() {
    SoundComponent._super.onEnable.call(this);
    if (this.system._inTools) {
      return;
    }
    var slots = this.data.slots;
    var playingBeforeDisable = this.data.playingBeforeDisable;
    for (var key in slots) {
      var slot = slots[key];
      if (slot.autoPlay && slot.isStopped) {
        slot.play();
      } else {
        if (playingBeforeDisable[key]) {
          slot.resume();
        } else {
          if (!slot.isLoaded) {
            slot.load();
          }
        }
      }
    }
  }, onDisable:function() {
    SoundComponent._super.onDisable.call(this);
    var slots = this.data.slots;
    var playingBeforeDisable = {};
    for (var key in slots) {
      if (!slots[key].overlap) {
        if (slots[key].isPlaying) {
          slots[key].pause();
          playingBeforeDisable[key] = true;
        }
      }
    }
    this.data.playingBeforeDisable = playingBeforeDisable;
  }, addSlot:function(name, options) {
    var slots = this.data.slots;
    if (slots[name]) {
      logWARNING("A sound slot with name " + name + " already exists on Entity " + this.entity.getPath());
      return null;
    }
    var slot = new pc.SoundSlot(this, name, options);
    slots[name] = slot;
    if (slot.autoPlay && this.enabled && this.entity.enabled) {
      slot.play();
    }
    return slot;
  }, removeSlot:function(name) {
    var slots = this.data.slots;
    if (slots[name]) {
      slots[name].stop();
      delete slots[name];
    }
  }, slot:function(name) {
    return this.data.slots[name];
  }, play:function(name) {
    if (!this.enabled || !this.entity.enabled) {
      return null;
    }
    var slot = this.slots[name];
    if (!slot) {
      logWARNING("Trying to play sound slot with name " + name + " which does not exist");
      return null;
    }
    return slot.play();
  }, pause:function(name) {
    var slot;
    var slots = this.data.slots;
    if (name) {
      slot = slots[name];
      if (!slot) {
        logWARNING("Trying to pause sound slot with name " + name + " which does not exist");
        return;
      }
      slot.pause();
    } else {
      for (var key in slots) {
        slots[key].pause();
      }
    }
  }, resume:function(name) {
    var slot;
    var slots = this.data.slots;
    if (name) {
      slot = slots[name];
      if (!slot) {
        logWARNING("Trying to resume sound slot with name " + name + " which does not exist");
        return;
      }
      if (slot.isPaused) {
        slot.resume();
      }
    } else {
      for (var key in slots) {
        slots[key].resume();
      }
    }
  }, stop:function(name) {
    var slot;
    var slots = this.data.slots;
    if (name) {
      slot = slots[name];
      if (!slot) {
        logWARNING("Trying to stop sound slot with name " + name + " which does not exist");
        return;
      }
      slot.stop();
    } else {
      for (var key in slots) {
        slots[key].stop();
      }
    }
  }});
  return {SoundComponent:SoundComponent};
}());
pc.SoundComponentData = function SoundComponentData() {
  this.enabled = true;
  this.volume = 1;
  this.pitch = 1;
  this.positional = true;
  this.refDistance = 1;
  this.maxDistance = 1E4;
  this.rollOffFactor = 1;
  this.distanceModel = pc.DISTANCE_LINEAR;
  this.slots = {};
  this.playingBeforeDisable = {};
};
pc.extend(pc, function() {
  var AudioSourceComponentSystem = function(app, manager) {
    this.id = "audiosource";
    this.description = "Specifies audio assets that can be played at the position of the Entity.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.AudioSourceComponent;
    this.DataType = pc.AudioSourceComponentData;
    this.schema = ["enabled", "assets", "volume", "pitch", "loop", "activate", "3d", "minDistance", "maxDistance", "rollOffFactor", "distanceModel", "sources", "currentSource", "channel"];
    this.manager = manager;
    this.initialized = false;
    pc.ComponentSystem.on("initialize", this.onInitialize, this);
    pc.ComponentSystem.on("update", this.onUpdate, this);
    this.on("remove", this.onRemove, this);
  };
  AudioSourceComponentSystem = pc.inherits(AudioSourceComponentSystem, pc.ComponentSystem);
  pc.extend(AudioSourceComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    properties = ["activate", "volume", "pitch", "loop", "3d", "minDistance", "maxDistance", "rollOffFactor", "distanceModel", "enabled", "assets"];
    AudioSourceComponentSystem._super.initializeComponentData.call(this, component, data, properties);
    component.paused = !(component.enabled && component.activate);
  }, onInitialize:function(root) {
    if (root.audiosource && root.enabled && root.audiosource.enabled && root.audiosource.activate) {
      root.audiosource.play(root.audiosource.currentSource);
    }
    var children = root._children;
    var i, len = children.length;
    for (i = 0;i < len;i++) {
      if (children[i] instanceof pc.Entity) {
        this.onInitialize(children[i]);
      }
    }
    this.initialized = true;
  }, onUpdate:function(dt) {
    var components = this.store;
    for (var id in components) {
      if (components.hasOwnProperty(id)) {
        var component = components[id];
        var entity = component.entity;
        var componentData = component.data;
        if (componentData.enabled && entity.enabled && componentData.channel instanceof pc.Channel3d) {
          var pos = entity.getPosition();
          componentData.channel.setPosition(pos);
        }
      }
    }
  }, onRemove:function(entity, data) {
    if (data.channel) {
      data.channel.stop();
      data.channel = null;
    }
  }, setVolume:function(volume) {
    this.manager.setVolume(volume);
  }});
  return {AudioSourceComponentSystem:AudioSourceComponentSystem};
}());
pc.extend(pc, function() {
  var AudioSourceComponent = function(system, entity) {
    this.on("set_assets", this.onSetAssets, this);
    this.on("set_loop", this.onSetLoop, this);
    this.on("set_volume", this.onSetVolume, this);
    this.on("set_pitch", this.onSetPitch, this);
    this.on("set_minDistance", this.onSetMinDistance, this);
    this.on("set_maxDistance", this.onSetMaxDistance, this);
    this.on("set_rollOffFactor", this.onSetRollOffFactor, this);
    this.on("set_distanceModel", this.onSetDistanceModel, this);
    this.on("set_3d", this.onSet3d, this);
  };
  AudioSourceComponent = pc.inherits(AudioSourceComponent, pc.Component);
  pc.extend(AudioSourceComponent.prototype, {play:function(name) {
    if (!this.enabled || !this.entity.enabled) {
      return;
    }
    if (this.channel) {
      this.stop();
    }
    var channel;
    var componentData = this.data;
    if (componentData.sources[name]) {
      if (!componentData["3d"]) {
        channel = this.system.manager.playSound(componentData.sources[name], componentData);
        componentData.currentSource = name;
        componentData.channel = channel;
      } else {
        var pos = this.entity.getPosition();
        channel = this.system.manager.playSound3d(componentData.sources[name], pos, componentData);
        componentData.currentSource = name;
        componentData.channel = channel;
      }
    }
  }, pause:function() {
    if (this.channel) {
      this.channel.pause();
    }
  }, unpause:function() {
    if (this.channel && this.channel.paused) {
      this.channel.unpause();
    }
  }, stop:function() {
    if (this.channel) {
      this.channel.stop();
      this.channel = null;
    }
  }, onSetAssets:function(name, oldValue, newValue) {
    var componentData = this.data;
    var newAssets = [];
    var i, len = newValue.length;
    if (oldValue && oldValue.length) {
      for (i = 0;i < oldValue.length;i++) {
        if (oldValue[i]) {
          var asset = this.system.app.assets.get(oldValue[i]);
          if (asset) {
            asset.off("change", this.onAssetChanged, this);
            asset.off("remove", this.onAssetRemoved, this);
            if (this.currentSource === asset.name) {
              this.stop();
            }
          }
        }
      }
    }
    if (len) {
      for (i = 0;i < len;i++) {
        if (oldValue.indexOf(newValue[i]) < 0) {
          if (newValue[i] instanceof pc.Asset) {
            newAssets.push(newValue[i].id);
          } else {
            newAssets.push(newValue[i]);
          }
        }
      }
    }
    if (!this.system._inTools && newAssets.length) {
      this.loadAudioSourceAssets(newAssets);
    }
  }, onAssetChanged:function(asset, attribute, newValue, oldValue) {
    if (attribute === "resource") {
      var sources = this.data.sources;
      if (sources) {
        this.data.sources[asset.name] = newValue;
        if (this.data.currentSource === asset.name) {
          if (this.channel) {
            if (this.channel.paused) {
              this.play(asset.name);
              this.pause();
            } else {
              this.play(asset.name);
            }
          }
        }
      }
    }
  }, onAssetRemoved:function(asset) {
    asset.off("remove", this.onAssetRemoved, this);
    if (this.data.sources[asset.name]) {
      delete this.data.sources[asset.name];
      if (this.data.currentSource === asset.name) {
        this.stop();
        this.data.currentSource = null;
      }
    }
  }, onSetLoop:function(name, oldValue, newValue) {
    if (oldValue != newValue) {
      if (this.channel) {
        this.channel.setLoop(newValue);
      }
    }
  }, onSetVolume:function(name, oldValue, newValue) {
    if (oldValue != newValue) {
      if (this.channel) {
        this.channel.setVolume(newValue);
      }
    }
  }, onSetPitch:function(name, oldValue, newValue) {
    if (oldValue != newValue) {
      if (this.channel) {
        this.channel.setPitch(newValue);
      }
    }
  }, onSetMaxDistance:function(name, oldValue, newValue) {
    if (oldValue != newValue) {
      if (this.channel instanceof pc.Channel3d) {
        this.channel.setMaxDistance(newValue);
      }
    }
  }, onSetMinDistance:function(name, oldValue, newValue) {
    if (oldValue != newValue) {
      if (this.channel instanceof pc.Channel3d) {
        this.channel.setMinDistance(newValue);
      }
    }
  }, onSetRollOffFactor:function(name, oldValue, newValue) {
    if (oldValue != newValue) {
      if (this.channel instanceof pc.Channel3d) {
        this.channel.setRollOffFactor(newValue);
      }
    }
  }, onSetDistanceModel:function(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      if (this.channel instanceof pc.Channel3d) {
        this.channel.setDistanceModel(newValue);
      }
    }
  }, onSet3d:function(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      if (this.system.initialized && this.currentSource) {
        var paused = false;
        var suspended = false;
        if (this.channel) {
          paused = this.channel.paused;
          suspended = this.channel.suspended;
        }
        this.play(this.currentSource);
        if (this.channel) {
          this.channel.paused = paused;
          this.channel.suspended = suspended;
        }
      }
    }
  }, onEnable:function() {
    AudioSourceComponent._super.onEnable.call(this);
    var assets = this.data.assets;
    if (assets) {
      var registry = this.system.app.assets;
      for (var i = 0, len = assets.length;i < len;i++) {
        var asset = assets[i];
        if (!(asset instanceof pc.Asset)) {
          asset = registry.get(asset);
        }
        if (asset && !asset.resource) {
          registry.load(asset);
        }
      }
    }
    if (this.system.initialized) {
      if (this.data.activate && !this.channel) {
        this.play(this.currentSource);
      } else {
        this.unpause();
      }
    }
  }, onDisable:function() {
    AudioSourceComponent._super.onDisable.call(this);
    this.pause();
  }, loadAudioSourceAssets:function(ids) {
    var self = this;
    var assets = ids.map(function(id) {
      return this.system.app.assets.get(id);
    }, this);
    var sources = {};
    var currentSource = null;
    var count = assets.length;
    var _error = function(e) {
      count--;
    };
    var _done = function() {
      this.data.sources = sources;
      this.data.currentSource = currentSource;
      if (this.enabled && this.activate && currentSource) {
        this.onEnable();
      }
    }.bind(this);
    assets.forEach(function(asset, index) {
      if (asset) {
        currentSource = currentSource || asset.name;
        asset.off("change", this.onAssetChanged, this);
        asset.on("change", this.onAssetChanged, this);
        asset.off("remove", this.onAssetRemoved, this);
        asset.on("remove", this.onAssetRemoved, this);
        asset.off("error", _error, this);
        asset.on("error", _error, this);
        asset.ready(function(asset) {
          sources[asset.name] = asset.resource;
          count--;
          if (count === 0) {
            _done();
          }
        });
        if (!asset.resource && self.enabled && self.entity.enabled) {
          this.system.app.assets.load(asset);
        }
      } else {
        count--;
        if (count === 0) {
          _done();
        }
        this.system.app.assets.on("add:" + ids[index], function(asset) {
          asset.ready(function(asset) {
            self.data.sources[asset.name] = asset.resource;
          });
          if (!asset.resource) {
            self.system.app.assets.load(asset);
          }
        });
      }
    }, this);
  }});
  return {AudioSourceComponent:AudioSourceComponent};
}());
pc.AudioSourceComponentData = function AudioSourceComponentData() {
  this.enabled = true;
  this.assets = [];
  this.activate = true;
  this.volume = 1;
  this.pitch = 1;
  this.loop = false;
  this["3d"] = true;
  this.minDistance = 1;
  this.maxDistance = 1E4;
  this.rollOffFactor = 1;
  this.distanceModel = pc.DISTANCE_INVERSE;
  this.paused = true;
  this.sources = {};
  this.currentSource = null;
  this.channel = null;
};
pc.extend(pc, function() {
  var AudioListenerComponentSystem = function(app, manager) {
    this.id = "audiolistener";
    this.description = "Specifies the location of the listener for 3D audio playback.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.AudioListenerComponent;
    this.DataType = pc.AudioListenerComponentData;
    this.schema = ["enabled"];
    this.manager = manager;
    this.current = null;
    pc.ComponentSystem.on("update", this.onUpdate, this);
  };
  AudioListenerComponentSystem = pc.inherits(AudioListenerComponentSystem, pc.ComponentSystem);
  pc.extend(AudioListenerComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    properties = ["enabled"];
    AudioListenerComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, onUpdate:function(dt) {
    if (this.current) {
      var position = this.current.getPosition();
      this.manager.listener.setPosition(position);
      var wtm = this.current.getWorldTransform();
      this.manager.listener.setOrientation(wtm);
    }
  }});
  return {AudioListenerComponentSystem:AudioListenerComponentSystem};
}());
pc.extend(pc, function() {
  var AudioListenerComponent = function(system, entity) {
  };
  AudioListenerComponent = pc.inherits(AudioListenerComponent, pc.Component);
  pc.extend(AudioListenerComponent.prototype, {setCurrentListener:function() {
    if (this.enabled && this.entity.audiolistener && this.entity.enabled) {
      this.system.current = this.entity;
      var position = this.system.current.getPosition();
      this.system.manager.listener.setPosition(position);
    }
  }, onEnable:function() {
    AudioListenerComponent._super.onEnable.call(this);
    this.setCurrentListener();
  }, onDisable:function() {
    AudioListenerComponent._super.onDisable.call(this);
    if (this.system.current === this.entity) {
      this.system.current = null;
    }
  }});
  return {AudioListenerComponent:AudioListenerComponent};
}());
pc.extend(pc, function() {
  var AudioListenerComponentData = function() {
    this.enabled = true;
  };
  AudioListenerComponentData = pc.inherits(AudioListenerComponentData, pc.ComponentData);
  return {AudioListenerComponentData:AudioListenerComponentData};
}());
pc.extend(pc, {BODYTYPE_STATIC:"static", BODYTYPE_DYNAMIC:"dynamic", BODYTYPE_KINEMATIC:"kinematic", BODYFLAG_STATIC_OBJECT:1, BODYFLAG_KINEMATIC_OBJECT:2, BODYFLAG_NORESPONSE_OBJECT:4, BODYSTATE_ACTIVE_TAG:1, BODYSTATE_ISLAND_SLEEPING:2, BODYSTATE_WANTS_DEACTIVATION:3, BODYSTATE_DISABLE_DEACTIVATION:4, BODYSTATE_DISABLE_SIMULATION:5, BODYGROUP_NONE:0, BODYGROUP_DEFAULT:1, BODYGROUP_DYNAMIC:1, BODYGROUP_STATIC:2, BODYGROUP_KINEMATIC:4, BODYGROUP_ENGINE_1:8, BODYGROUP_TRIGGER:16, BODYGROUP_ENGINE_2:32, 
BODYGROUP_ENGINE_3:64, BODYGROUP_USER_1:128, BODYGROUP_USER_2:256, BODYGROUP_USER_3:512, BODYGROUP_USER_4:1024, BODYGROUP_USER_5:2048, BODYGROUP_USER_6:4096, BODYGROUP_USER_7:8192, BODYGROUP_USER_8:16384, BODYMASK_NONE:0, BODYMASK_ALL:65535, BODYMASK_STATIC:2, BODYMASK_NOT_STATIC:65535 ^ 2, BODYMASK_NOT_STATIC_KINEMATIC:65535 ^ (2 | 4)});
pc.extend(pc, function() {
  var transform = new pc.Mat4;
  var newWtm = new pc.Mat4;
  var position = new pc.Vec3;
  var rotation = new pc.Vec3;
  var scale = new pc.Vec3;
  var ammoRayStart, ammoRayEnd;
  var collisions = {};
  var frameCollisions = {};
  var WARNED_RAYCAST_CALLBACK = false;
  var RaycastResult = function RaycastResult(entity, point, normal) {
    this.entity = entity;
    this.point = point;
    this.normal = normal;
  };
  var SingleContactResult = function SingleContactResult(a, b, contactPoint) {
    if (arguments.length === 0) {
      this.a = null;
      this.b = null;
      this.localPointA = new pc.Vec3;
      this.localPointB = new pc.Vec3;
      this.pointA = new pc.Vec3;
      this.pointB = new pc.Vec3;
      this.normal = new pc.Vec3;
    } else {
      this.a = a;
      this.b = b;
      this.localPointA = contactPoint.localPoint;
      this.localPointB = contactPoint.localPointOther;
      this.pointA = contactPoint.point;
      this.pointB = contactPoint.pointOther;
      this.normal = contactPoint.normal;
    }
  };
  var ContactPoint = function ContactPoint(localPoint, localPointOther, point, pointOther, normal) {
    if (arguments.length === 0) {
      this.localPoint = new pc.Vec3;
      this.localPointOther = new pc.Vec3;
      this.point = new pc.Vec3;
      this.pointOther = new pc.Vec3;
      this.normal = new pc.Vec3;
    } else {
      this.localPoint = localPoint;
      this.localPointOther = localPointOther;
      this.point = point;
      this.pointOther = pointOther;
      this.normal = normal;
    }
  };
  var ContactResult = function ContactResult(other, contacts) {
    this.other = other;
    this.contacts = contacts;
  };
  var RigidBodyComponentSystem = function RigidBodyComponentSystem(app) {
    this.id = "rigidbody";
    this.description = "Adds the entity to the scene's physical simulation.";
    app.systems.add(this.id, this);
    this._stats = app.stats.frame;
    this.ComponentType = pc.RigidBodyComponent;
    this.DataType = pc.RigidBodyComponentData;
    this.contactPointPool = new pc.AllocatePool(ContactPoint, 1);
    this.contactResultPool = new pc.AllocatePool(ContactResult, 1);
    this.singleContactResultPool = new pc.AllocatePool(SingleContactResult, 1);
    this.schema = ["enabled", "type", "mass", "linearDamping", "angularDamping", "linearFactor", "angularFactor", "friction", "restitution", "group", "mask", "body"];
    this.maxSubSteps = 10;
    this.fixedTimeStep = 1 / 60;
    this.on("remove", this.onRemove, this);
  };
  RigidBodyComponentSystem = pc.inherits(RigidBodyComponentSystem, pc.ComponentSystem);
  pc.extend(RigidBodyComponentSystem.prototype, {onLibraryLoaded:function() {
    if (typeof Ammo !== "undefined") {
      var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration;
      var dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
      var overlappingPairCache = new Ammo.btDbvtBroadphase;
      var solver = new Ammo.btSequentialImpulseConstraintSolver;
      this.dynamicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
      this._ammoGravity = new Ammo.btVector3(0, -9.82, 0);
      this.dynamicsWorld.setGravity(this._ammoGravity);
      ammoRayStart = new Ammo.btVector3;
      ammoRayEnd = new Ammo.btVector3;
      pc.ComponentSystem.on("update", this.onUpdate, this);
    } else {
      pc.ComponentSystem.off("update", this.onUpdate, this);
    }
  }, initializeComponentData:function(component, _data, properties) {
    properties = ["enabled", "mass", "linearDamping", "angularDamping", "linearFactor", "angularFactor", "friction", "restitution", "type", "group", "mask"];
    var data = {};
    properties.forEach(function(prop) {
      data[prop] = _data[prop];
    });
    if (_data.bodyType) {
      data.type = _data.bodyType;
      console.warn("WARNING: rigidbody.bodyType: Property is deprecated. Use type instead.");
    }
    if (data.linearFactor && pc.type(data.linearFactor) === "array") {
      data.linearFactor = new pc.Vec3(data.linearFactor[0], data.linearFactor[1], data.linearFactor[2]);
    }
    if (data.angularFactor && pc.type(data.angularFactor) === "array") {
      data.angularFactor = new pc.Vec3(data.angularFactor[0], data.angularFactor[1], data.angularFactor[2]);
    }
    RigidBodyComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, cloneComponent:function(entity, clone) {
    var data = {enabled:entity.rigidbody.enabled, mass:entity.rigidbody.mass, linearDamping:entity.rigidbody.linearDamping, angularDamping:entity.rigidbody.angularDamping, linearFactor:[entity.rigidbody.linearFactor.x, entity.rigidbody.linearFactor.y, entity.rigidbody.linearFactor.z], angularFactor:[entity.rigidbody.angularFactor.x, entity.rigidbody.angularFactor.y, entity.rigidbody.angularFactor.z], friction:entity.rigidbody.friction, restitution:entity.rigidbody.restitution, type:entity.rigidbody.type, 
    group:entity.rigidbody.group, mask:entity.rigidbody.mask};
    this.addComponent(clone, data);
  }, onRemove:function(entity, data) {
    if (data.body) {
      this.removeBody(data.body);
      Ammo.destroy(data.body);
    }
    data.body = null;
  }, addBody:function(body, group, mask) {
    if (group !== undefined && mask !== undefined) {
      this.dynamicsWorld.addRigidBody(body, group, mask);
    } else {
      this.dynamicsWorld.addRigidBody(body);
    }
    return body;
  }, removeBody:function(body) {
    this.dynamicsWorld.removeRigidBody(body);
  }, addConstraint:function(constraint) {
    this.dynamicsWorld.addConstraint(constraint);
    return constraint;
  }, removeConstraint:function(constraint) {
    this.dynamicsWorld.removeConstraint(constraint);
  }, setGravity:function() {
    var x, y, z;
    if (arguments.length === 1) {
      x = arguments[0].x;
      y = arguments[0].y;
      z = arguments[0].z;
    } else {
      x = arguments[0];
      y = arguments[1];
      z = arguments[2];
    }
    this._ammoGravity.setValue(x, y, z);
    this.dynamicsWorld.setGravity(this._ammoGravity);
  }, raycastFirst:function(start, end, callback) {
    var result = null;
    ammoRayStart.setValue(start.x, start.y, start.z);
    ammoRayEnd.setValue(end.x, end.y, end.z);
    var rayCallback = new Ammo.ClosestRayResultCallback(ammoRayStart, ammoRayEnd);
    this.dynamicsWorld.rayTest(ammoRayStart, ammoRayEnd, rayCallback);
    if (rayCallback.hasHit()) {
      var collisionObj = rayCallback.get_m_collisionObject();
      var body = Ammo.castObject(collisionObj, Ammo.btRigidBody);
      if (body) {
        var point = rayCallback.get_m_hitPointWorld();
        var normal = rayCallback.get_m_hitNormalWorld();
        result = new RaycastResult(body.entity, new pc.Vec3(point.x(), point.y(), point.z()), new pc.Vec3(normal.x(), normal.y(), normal.z()));
        if (callback) {
          callback(result);
          if (!WARNED_RAYCAST_CALLBACK) {
            console.warn("[DEPRECATED]: pc.RigidBodyComponentSystem#rayCastFirst no longer requires a callback. The result of the raycast is returned by the function instead.");
            WARNED_RAYCAST_CALLBACK = true;
          }
        }
      }
    }
    Ammo.destroy(rayCallback);
    return result;
  }, _storeCollision:function(entity, other) {
    var isNewCollision = false;
    var guid = entity._guid;
    collisions[guid] = collisions[guid] || {others:[], entity:entity};
    if (collisions[guid].others.indexOf(other) < 0) {
      collisions[guid].others.push(other);
      isNewCollision = true;
    }
    frameCollisions[guid] = frameCollisions[guid] || {others:[], entity:entity};
    frameCollisions[guid].others.push(other);
    return isNewCollision;
  }, _createContactPointFromAmmo:function(contactPoint) {
    var contact = this.contactPointPool.allocate();
    contact.localPoint.set(contactPoint.get_m_localPointA().x(), contactPoint.get_m_localPointA().y(), contactPoint.get_m_localPointA().z());
    contact.localPointOther.set(contactPoint.get_m_localPointB().x(), contactPoint.get_m_localPointB().y(), contactPoint.get_m_localPointB().z());
    contact.point.set(contactPoint.getPositionWorldOnA().x(), contactPoint.getPositionWorldOnA().y(), contactPoint.getPositionWorldOnA().z());
    contact.pointOther.set(contactPoint.getPositionWorldOnB().x(), contactPoint.getPositionWorldOnB().y(), contactPoint.getPositionWorldOnB().z());
    contact.normal.set(contactPoint.get_m_normalWorldOnB().x(), contactPoint.get_m_normalWorldOnB().y(), contactPoint.get_m_normalWorldOnB().z());
    return contact;
  }, _createReverseContactPointFromAmmo:function(contactPoint) {
    var contact = this.contactPointPool.allocate();
    contact.localPointOther.set(contactPoint.get_m_localPointA().x(), contactPoint.get_m_localPointA().y(), contactPoint.get_m_localPointA().z());
    contact.localPoint.set(contactPoint.get_m_localPointB().x(), contactPoint.get_m_localPointB().y(), contactPoint.get_m_localPointB().z());
    contact.pointOther.set(contactPoint.getPositionWorldOnA().x(), contactPoint.getPositionWorldOnA().y(), contactPoint.getPositionWorldOnA().z());
    contact.point.set(contactPoint.getPositionWorldOnB().x(), contactPoint.getPositionWorldOnB().y(), contactPoint.getPositionWorldOnB().z());
    contact.normal.set(contactPoint.get_m_normalWorldOnB().x(), contactPoint.get_m_normalWorldOnB().y(), contactPoint.get_m_normalWorldOnB().z());
    return contact;
  }, _createSingleContactResult:function(a, b, contactPoint) {
    var result = this.singleContactResultPool.allocate();
    result.a = a;
    result.b = b;
    result.localPointA = contactPoint.localPoint;
    result.localPointB = contactPoint.localPointOther;
    result.pointA = contactPoint.point;
    result.pointB = contactPoint.pointOther;
    result.normal = contactPoint.normal;
    return result;
  }, _createContactResult:function(other, contacts) {
    var result = this.contactResultPool.allocate();
    result.other = other;
    result.contacts = contacts;
    return result;
  }, _cleanOldCollisions:function() {
    for (var guid in collisions) {
      if (collisions.hasOwnProperty(guid)) {
        var entity = collisions[guid].entity;
        var entityCollision = entity.collision;
        var others = collisions[guid].others;
        var length = others.length;
        var i = length;
        while (i--) {
          var other = others[i];
          if (!frameCollisions[guid] || frameCollisions[guid].others.indexOf(other) < 0) {
            others.splice(i, 1);
            if (entityCollision && other.collision) {
              if (entity.rigidbody && other.rigidbody) {
                entityCollision.fire("collisionend", other);
              } else {
                if (entity.trigger) {
                  entityCollision.fire("triggerleave", other);
                }
              }
            }
          }
        }
        if (others.length === 0) {
          delete collisions[guid];
        }
      }
    }
  }, onUpdate:function(dt) {
    var frameContacts = 0;
    this.dynamicsWorld.stepSimulation(dt, this.maxSubSteps, this.fixedTimeStep);
    var components = this.store;
    for (var id in components) {
      if (components.hasOwnProperty(id)) {
        var entity = components[id].entity;
        var componentData = components[id].data;
        if (componentData.body && componentData.body.isActive() && componentData.enabled && entity.enabled) {
          if (componentData.type === pc.BODYTYPE_DYNAMIC) {
            entity.rigidbody.syncBodyToEntity();
          } else {
            if (componentData.type === pc.BODYTYPE_KINEMATIC) {
              entity.rigidbody._updateKinematic(dt);
            }
          }
        }
      }
    }
    var dispatcher = this.dynamicsWorld.getDispatcher();
    var numManifolds = dispatcher.getNumManifolds();
    var i, j;
    frameCollisions = {};
    for (i = 0;i < numManifolds;i++) {
      var manifold = dispatcher.getManifoldByIndexInternal(i);
      var body0 = manifold.getBody0();
      var body1 = manifold.getBody1();
      var wb0 = Ammo.castObject(body0, Ammo.btRigidBody);
      var wb1 = Ammo.castObject(body1, Ammo.btRigidBody);
      var e0 = wb0.entity;
      var e1 = wb1.entity;
      if (!e0 || !e1) {
        continue;
      }
      var flags0 = body0.getCollisionFlags();
      var flags1 = body1.getCollisionFlags();
      var numContacts = manifold.getNumContacts();
      var forwardContacts = [];
      var reverseContacts = [];
      var newCollision, e0Events, e1Events;
      if (numContacts > 0) {
        if (flags0 & pc.BODYFLAG_NORESPONSE_OBJECT || flags1 & pc.BODYFLAG_NORESPONSE_OBJECT) {
          e0Events = e0.collision ? e0.collision.hasEvent("triggerenter") || e0.collision.hasEvent("triggerleave") : false;
          e1Events = e1.collision ? e1.collision.hasEvent("triggerenter") || e1.collision.hasEvent("triggerleave") : false;
          if (e0Events) {
            newCollision = this._storeCollision(e0, e1);
            if (newCollision) {
              if (e0.collision && !(flags1 & pc.BODYFLAG_NORESPONSE_OBJECT)) {
                e0.collision.fire("triggerenter", e1);
              }
            }
          }
          if (e1Events) {
            newCollision = this._storeCollision(e1, e0);
            if (newCollision) {
              if (e1.collision && !(flags0 & pc.BODYFLAG_NORESPONSE_OBJECT)) {
                e1.collision.fire("triggerenter", e0);
              }
            }
          }
        } else {
          e0Events = e0.collision ? e0.collision.hasEvent("collisionstart") || e0.collision.hasEvent("collisionend") || e0.collision.hasEvent("contact") : false;
          e1Events = e1.collision ? e1.collision.hasEvent("collisionstart") || e1.collision.hasEvent("collisionend") || e1.collision.hasEvent("contact") : false;
          var globalEvents = this.hasEvent("contact");
          if (globalEvents || e0Events || e1Events) {
            for (j = 0;j < numContacts;j++) {
              var btContactPoint = manifold.getContactPoint(j);
              var contactPoint = this._createContactPointFromAmmo(btContactPoint);
              var reverseContactPoint = null;
              if (e0Events || e1Events) {
                reverseContactPoint = this._createReverseContactPointFromAmmo(btContactPoint);
                forwardContacts.push(contactPoint);
                reverseContacts.push(reverseContactPoint);
              }
              if (globalEvents) {
                var result = this._createSingleContactResult(e0, e1, contactPoint);
                this.fire("contact", result);
              }
            }
            if (e0Events) {
              var forwardResult = this._createContactResult(e1, forwardContacts);
              if (e0.collision) {
                e0.collision.fire("contact", forwardResult);
              }
              newCollision = this._storeCollision(e0, e1);
              if (newCollision && e0.collision) {
                e0.collision.fire("collisionstart", forwardResult);
              }
            }
            if (e1Events) {
              var reverseResult = this._createContactResult(e0, reverseContacts);
              if (e1.collision) {
                e1.collision.fire("contact", reverseResult);
              }
              newCollision = this._storeCollision(e1, e0);
              if (newCollision && e1.collision) {
                e1.collision.fire("collisionstart", reverseResult);
              }
            }
          }
        }
      }
    }
    this._cleanOldCollisions();
    this.contactPointPool.freeAll();
    this.contactResultPool.freeAll();
    this.singleContactResultPool.freeAll();
  }});
  return {RIGIDBODY_TYPE_STATIC:"static", RIGIDBODY_TYPE_DYNAMIC:"dynamic", RIGIDBODY_TYPE_KINEMATIC:"kinematic", RIGIDBODY_CF_STATIC_OBJECT:1, RIGIDBODY_CF_KINEMATIC_OBJECT:2, RIGIDBODY_CF_NORESPONSE_OBJECT:4, RIGIDBODY_ACTIVE_TAG:1, RIGIDBODY_ISLAND_SLEEPING:2, RIGIDBODY_WANTS_DEACTIVATION:3, RIGIDBODY_DISABLE_DEACTIVATION:4, RIGIDBODY_DISABLE_SIMULATION:5, RigidBodyComponentSystem:RigidBodyComponentSystem};
}());
pc.extend(pc, function() {
  var ammoTransform;
  var ammoVec1, ammoVec2, ammoQuat, ammoOrigin;
  var RigidBodyComponent = function RigidBodyComponent(system, entity) {
    if (typeof Ammo !== "undefined" && !ammoTransform) {
      ammoTransform = new Ammo.btTransform;
      ammoVec1 = new Ammo.btVector3;
      ammoVec2 = new Ammo.btVector3;
      ammoQuat = new Ammo.btQuaternion;
      ammoOrigin = new Ammo.btVector3(0, 0, 0);
    }
    this.on("set_mass", this.onSetMass, this);
    this.on("set_linearDamping", this.onSetLinearDamping, this);
    this.on("set_angularDamping", this.onSetAngularDamping, this);
    this.on("set_linearFactor", this.onSetLinearFactor, this);
    this.on("set_angularFactor", this.onSetAngularFactor, this);
    this.on("set_friction", this.onSetFriction, this);
    this.on("set_restitution", this.onSetRestitution, this);
    this.on("set_type", this.onSetType, this);
    this.on("set_group", this.onSetGroupOrMask, this);
    this.on("set_mask", this.onSetGroupOrMask, this);
    this.on("set_body", this.onSetBody, this);
    this._displacement = new pc.Vec3(0, 0, 0);
    this._linearVelocity = new pc.Vec3(0, 0, 0);
    this._angularVelocity = new pc.Vec3(0, 0, 0);
  };
  RigidBodyComponent = pc.inherits(RigidBodyComponent, pc.Component);
  Object.defineProperty(RigidBodyComponent.prototype, "bodyType", {get:function() {
    console.warn("WARNING: bodyType: Function is deprecated. Query type property instead.");
    return this.type;
  }, set:function(type) {
    console.warn("WARNING: bodyType: Function is deprecated. Set type property instead.");
    this.type = type;
  }});
  Object.defineProperty(RigidBodyComponent.prototype, "linearVelocity", {get:function() {
    if (!this.isKinematic()) {
      if (this.body) {
        var vel = this.body.getLinearVelocity();
        this._linearVelocity.set(vel.x(), vel.y(), vel.z());
        return this._linearVelocity;
      }
    } else {
      return this._linearVelocity;
    }
  }, set:function(lv) {
    this.activate();
    if (!this.isKinematic()) {
      var body = this.body;
      if (body) {
        ammoVec1.setValue(lv.x, lv.y, lv.z);
        body.setLinearVelocity(ammoVec1);
      }
    } else {
      this._linearVelocity.copy(lv);
    }
  }});
  Object.defineProperty(RigidBodyComponent.prototype, "angularVelocity", {get:function() {
    if (!this.isKinematic()) {
      if (this.body) {
        var vel = this.body.getAngularVelocity();
        this._angularVelocity.set(vel.x(), vel.y(), vel.z());
        return this._angularVelocity;
      }
    } else {
      return this._angularVelocity;
    }
  }, set:function(av) {
    this.activate();
    if (!this.isKinematic()) {
      var body = this.body;
      if (body) {
        ammoVec1.setValue(av.x, av.y, av.z);
        body.setAngularVelocity(ammoVec1);
      }
    } else {
      this._angularVelocity.copy(av);
    }
  }});
  pc.extend(RigidBodyComponent.prototype, {createBody:function() {
    var entity = this.entity;
    var shape;
    if (entity.collision) {
      shape = entity.collision.shape;
      if (entity.trigger) {
        entity.trigger.destroy();
        delete entity.trigger;
      }
    }
    if (shape) {
      if (this.body) {
        this.system.removeBody(this.body);
        Ammo.destroy(this.body);
      }
      var isStaticOrKinematic = this.isStaticOrKinematic();
      var mass = isStaticOrKinematic ? 0 : this.mass;
      var localInertia = new Ammo.btVector3(0, 0, 0);
      if (!isStaticOrKinematic) {
        shape.calculateLocalInertia(mass, localInertia);
      }
      var pos = entity.getPosition();
      var rot = entity.getRotation();
      ammoQuat.setValue(rot.x, rot.y, rot.z, rot.w);
      var startTransform = new Ammo.btTransform;
      startTransform.setIdentity();
      startTransform.getOrigin().setValue(pos.x, pos.y, pos.z);
      startTransform.setRotation(ammoQuat);
      var motionState = new Ammo.btDefaultMotionState(startTransform);
      var bodyInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
      var body = new Ammo.btRigidBody(bodyInfo);
      body.setRestitution(this.restitution);
      body.setFriction(this.friction);
      body.setDamping(this.linearDamping, this.angularDamping);
      var v;
      v = this.linearFactor;
      ammoVec1.setValue(v.x, v.y, v.z);
      body.setLinearFactor(ammoVec1);
      v = this.angularFactor;
      ammoVec1.setValue(v.x, v.y, v.z);
      body.setAngularFactor(ammoVec1);
      body.entity = entity;
      if (this.isKinematic()) {
        body.setCollisionFlags(body.getCollisionFlags() | pc.BODYFLAG_KINEMATIC_OBJECT);
        body.setActivationState(pc.BODYSTATE_DISABLE_DEACTIVATION);
      }
      entity.rigidbody.body = body;
      if (this.enabled && this.entity.enabled) {
        this.enableSimulation();
      }
    }
  }, isActive:function() {
    if (this.body) {
      return this.body.isActive();
    }
    return false;
  }, activate:function() {
    if (this.body) {
      this.body.activate();
    }
  }, enableSimulation:function() {
    if (this.entity.collision && this.entity.collision.enabled && !this.data.simulationEnabled) {
      var body = this.body;
      if (body) {
        this.system.addBody(body, this.group, this.mask);
        if (this.isKinematic()) {
          body.forceActivationState(pc.BODYSTATE_DISABLE_DEACTIVATION);
          body.activate();
        } else {
          body.forceActivationState(pc.BODYFLAG_ACTIVE_TAG);
          this.syncEntityToBody();
        }
        this.data.simulationEnabled = true;
      }
    }
  }, disableSimulation:function() {
    var body = this.body;
    if (body && this.data.simulationEnabled) {
      this.system.removeBody(body);
      body.forceActivationState(pc.BODYSTATE_DISABLE_SIMULATION);
      this.data.simulationEnabled = false;
    }
  }, applyForce:function() {
    var x, y, z;
    var px, py, pz;
    switch(arguments.length) {
      case 1:
        x = arguments[0].x;
        y = arguments[0].y;
        z = arguments[0].z;
        break;
      case 2:
        x = arguments[0].x;
        y = arguments[0].y;
        z = arguments[0].z;
        px = arguments[1].x;
        py = arguments[1].y;
        pz = arguments[1].z;
        break;
      case 3:
        x = arguments[0];
        y = arguments[1];
        z = arguments[2];
        break;
      case 6:
        x = arguments[0];
        y = arguments[1];
        z = arguments[2];
        px = arguments[3];
        py = arguments[4];
        pz = arguments[5];
        break;
    }
    var body = this.body;
    if (body) {
      body.activate();
      ammoVec1.setValue(x, y, z);
      if (px !== undefined) {
        ammoVec2.setValue(px, py, pz);
        body.applyForce(ammoVec1, ammoVec2);
      } else {
        body.applyForce(ammoVec1, ammoOrigin);
      }
    }
  }, applyTorque:function() {
    var x, y, z;
    switch(arguments.length) {
      case 1:
        x = arguments[0].x;
        y = arguments[0].y;
        z = arguments[0].z;
        break;
      case 3:
        x = arguments[0];
        y = arguments[1];
        z = arguments[2];
        break;
      default:
        console.error("ERROR: applyTorque: function takes 1 or 3 arguments");
        return;
    }
    var body = this.body;
    if (body) {
      body.activate();
      ammoVec1.setValue(x, y, z);
      body.applyTorque(ammoVec1);
    }
  }, applyImpulse:function() {
    var x, y, z;
    var px, py, pz;
    switch(arguments.length) {
      case 1:
        x = arguments[0].x;
        y = arguments[0].y;
        z = arguments[0].z;
        break;
      case 2:
        x = arguments[0].x;
        y = arguments[0].y;
        z = arguments[0].z;
        px = arguments[1].x;
        py = arguments[1].y;
        pz = arguments[1].z;
        break;
      case 3:
        x = arguments[0];
        y = arguments[1];
        z = arguments[2];
        break;
      case 6:
        x = arguments[0];
        y = arguments[1];
        z = arguments[2];
        px = arguments[0];
        py = arguments[1];
        pz = arguments[2];
        break;
    }
    var body = this.body;
    if (body) {
      body.activate();
      ammoVec1.setValue(x, y, z);
      if (px !== undefined) {
        ammoVec2.setValue(px, py, pz);
        body.applyImpulse(ammoVec1, ammoVec2);
      } else {
        body.applyImpulse(ammoVec1, ammoOrigin);
      }
    }
  }, applyTorqueImpulse:function() {
    var x, y, z;
    switch(arguments.length) {
      case 1:
        x = arguments[0].x;
        y = arguments[0].y;
        z = arguments[0].z;
        break;
      case 3:
        x = arguments[0];
        y = arguments[1];
        z = arguments[2];
        break;
      default:
        console.error("ERROR: applyTorqueImpulse: function takes 1 or 3 arguments");
        return;
    }
    var body = this.body;
    if (body) {
      body.activate();
      ammoVec1.setValue(x, y, z);
      body.applyTorqueImpulse(ammoVec1);
    }
  }, isStatic:function() {
    return this.type === pc.BODYTYPE_STATIC;
  }, isStaticOrKinematic:function() {
    return this.type === pc.BODYTYPE_STATIC || this.type === pc.BODYTYPE_KINEMATIC;
  }, isKinematic:function() {
    return this.type === pc.BODYTYPE_KINEMATIC;
  }, syncEntityToBody:function() {
    var body = this.body;
    if (body) {
      var pos = this.entity.getPosition();
      var rot = this.entity.getRotation();
      var transform = body.getWorldTransform();
      transform.getOrigin().setValue(pos.x, pos.y, pos.z);
      ammoQuat.setValue(rot.x, rot.y, rot.z, rot.w);
      transform.setRotation(ammoQuat);
      if (this.isKinematic()) {
        var motionState = this.body.getMotionState();
        if (motionState) {
          motionState.setWorldTransform(transform);
        }
      }
      body.activate();
    }
  }, syncBodyToEntity:function() {
    var body = this.body;
    if (body.isActive()) {
      var motionState = body.getMotionState();
      if (motionState) {
        motionState.getWorldTransform(ammoTransform);
        var p = ammoTransform.getOrigin();
        var q = ammoTransform.getRotation();
        this.entity.setPosition(p.x(), p.y(), p.z());
        this.entity.setRotation(q.x(), q.y(), q.z(), q.w());
      }
    }
  }, teleport:function() {
    if (arguments.length < 3) {
      if (arguments[0]) {
        this.entity.setPosition(arguments[0]);
      }
      if (arguments[1]) {
        if (arguments[1] instanceof pc.Quat) {
          this.entity.setRotation(arguments[1]);
        } else {
          this.entity.setEulerAngles(arguments[1]);
        }
      }
    } else {
      if (arguments.length === 6) {
        this.entity.setEulerAngles(arguments[3], arguments[4], arguments[5]);
      }
      this.entity.setPosition(arguments[0], arguments[1], arguments[2]);
    }
    this.syncEntityToBody();
  }, _updateKinematic:function(dt) {
    this._displacement.copy(this._linearVelocity).scale(dt);
    this.entity.translate(this._displacement);
    this._displacement.copy(this._angularVelocity).scale(dt);
    this.entity.rotate(this._displacement.x, this._displacement.y, this._displacement.z);
    if (this.body.getMotionState()) {
      var pos = this.entity.getPosition();
      var rot = this.entity.getRotation();
      ammoTransform.getOrigin().setValue(pos.x, pos.y, pos.z);
      ammoQuat.setValue(rot.x, rot.y, rot.z, rot.w);
      ammoTransform.setRotation(ammoQuat);
      this.body.getMotionState().setWorldTransform(ammoTransform);
    }
  }, onEnable:function() {
    RigidBodyComponent._super.onEnable.call(this);
    if (!this.body) {
      this.createBody();
    }
    this.enableSimulation();
  }, onDisable:function() {
    RigidBodyComponent._super.onDisable.call(this);
    this.disableSimulation();
  }, onSetMass:function(name, oldValue, newValue) {
    var body = this.data.body;
    if (body) {
      var isEnabled = this.enabled && this.entity.enabled;
      if (isEnabled) {
        this.disableSimulation();
      }
      var mass = newValue;
      var localInertia = new Ammo.btVector3(0, 0, 0);
      body.getCollisionShape().calculateLocalInertia(mass, localInertia);
      body.setMassProps(mass, localInertia);
      body.updateInertiaTensor();
      if (isEnabled) {
        this.enableSimulation();
      }
    }
  }, onSetLinearDamping:function(name, oldValue, newValue) {
    var body = this.data.body;
    if (body) {
      body.setDamping(newValue, this.data.angularDamping);
    }
  }, onSetAngularDamping:function(name, oldValue, newValue) {
    var body = this.data.body;
    if (body) {
      body.setDamping(this.data.linearDamping, newValue);
    }
  }, onSetLinearFactor:function(name, oldValue, newValue) {
    var body = this.data.body;
    if (body) {
      ammoVec1.setValue(newValue.x, newValue.y, newValue.z);
      body.setLinearFactor(ammoVec1);
    }
  }, onSetAngularFactor:function(name, oldValue, newValue) {
    var body = this.data.body;
    if (body) {
      ammoVec1.setValue(newValue.x, newValue.y, newValue.z);
      body.setAngularFactor(ammoVec1);
    }
  }, onSetFriction:function(name, oldValue, newValue) {
    var body = this.data.body;
    if (body) {
      body.setFriction(newValue);
    }
  }, onSetRestitution:function(name, oldValue, newValue) {
    var body = this.data.body;
    if (body) {
      body.setRestitution(newValue);
    }
  }, onSetType:function(name, oldValue, newValue) {
    if (newValue !== oldValue) {
      this.disableSimulation();
      if (newValue === pc.BODYTYPE_DYNAMIC) {
        this.data.group = pc.BODYGROUP_DYNAMIC;
        this.data.mask = pc.BODYMASK_ALL;
      } else {
        if (newValue === pc.BODYTYPE_KINEMATIC) {
          this.data.group = pc.BODYGROUP_KINEMATIC;
          this.data.mask = pc.BODYMASK_ALL;
        } else {
          this.data.group = pc.BODYGROUP_STATIC;
          this.data.mask = pc.BODYMASK_NOT_STATIC;
        }
      }
      this.createBody();
    }
  }, onSetGroupOrMask:function(name, oldValue, newValue) {
    if (newValue !== oldValue) {
      var isEnabled = this.enabled && this.entity.enabled;
      if (isEnabled) {
        this.disableSimulation();
        this.enableSimulation();
      }
    }
  }, onSetBody:function(name, oldValue, newValue) {
    if (this.body && this.data.simulationEnabled) {
      this.body.activate();
    }
  }});
  return {RigidBodyComponent:RigidBodyComponent};
}());
pc.extend(pc, function() {
  var RigidBodyComponentData = function() {
    this.enabled = true;
    this.mass = 1;
    this.linearDamping = 0;
    this.angularDamping = 0;
    this.linearFactor = new pc.Vec3(1, 1, 1);
    this.angularFactor = new pc.Vec3(1, 1, 1);
    this.friction = .5;
    this.restitution = 0;
    this.type = pc.BODYTYPE_STATIC;
    this.group = pc.BODYGROUP_STATIC;
    this.mask = pc.BODYMASK_NOT_STATIC;
    this.body = null;
    this.simulationEnabled = false;
  };
  RigidBodyComponentData = pc.inherits(RigidBodyComponentData, pc.ComponentData);
  return {RigidBodyComponentData:RigidBodyComponentData};
}());
pc.extend(pc, function() {
  var ammoVec1, ammoQuat;
  var Trigger = function Trigger(app, component, data) {
    this.entity = component.entity;
    this.component = component;
    this.app = app;
    if (typeof Ammo !== "undefined") {
      ammoVec1 = new Ammo.btVector3;
      ammoQuat = new Ammo.btQuaternion;
    }
    this.initialize(data);
  };
  Trigger.prototype = {initialize:function(data) {
    var entity = this.entity;
    var shape = data.shape;
    if (shape && typeof Ammo !== "undefined") {
      if (entity.trigger) {
        entity.trigger.destroy();
      }
      var mass = 1;
      var localInertia = new Ammo.btVector3(0, 0, 0);
      shape.calculateLocalInertia(mass, localInertia);
      var pos = entity.getPosition();
      var rot = entity.getRotation();
      ammoQuat.setValue(rot.x, rot.y, rot.z, rot.w);
      var startTransform = new Ammo.btTransform;
      startTransform.setIdentity();
      startTransform.getOrigin().setValue(pos.x, pos.y, pos.z);
      startTransform.setRotation(ammoQuat);
      var motionState = new Ammo.btDefaultMotionState(startTransform);
      var bodyInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
      var body = new Ammo.btRigidBody(bodyInfo);
      this.body = body;
      body.setRestitution(0);
      body.setFriction(0);
      body.setDamping(0, 0);
      ammoVec1.setValue(0, 0, 0);
      body.setLinearFactor(ammoVec1);
      body.setAngularFactor(ammoVec1);
      body.setCollisionFlags(body.getCollisionFlags() | pc.BODYFLAG_NORESPONSE_OBJECT);
      body.entity = entity;
      if (this.component.enabled && entity.enabled) {
        this.enable();
      }
    }
  }, destroy:function() {
    if (this.body) {
      this.app.systems.rigidbody.removeBody(this.body);
    }
  }, syncEntityToBody:function() {
    var body = this.body;
    if (body) {
      var position = this.entity.getPosition();
      var rotation = this.entity.getRotation();
      var transform = body.getWorldTransform();
      transform.getOrigin().setValue(position.x, position.y, position.z);
      ammoQuat.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
      transform.setRotation(ammoQuat);
      body.activate();
    }
  }, enable:function() {
    var body = this.body;
    if (!body) {
      return;
    }
    this.app.systems.rigidbody.addBody(body, pc.BODYGROUP_TRIGGER, pc.BODYMASK_NOT_STATIC ^ pc.BODYGROUP_TRIGGER);
    body.forceActivationState(pc.BODYSTATE_ACTIVE_TAG);
    body.activate();
    this.syncEntityToBody();
  }, disable:function() {
    var body = this.body;
    if (!body) {
      return;
    }
    this.app.systems.rigidbody.removeBody(body);
    body.forceActivationState(pc.BODYSTATE_DISABLE_SIMULATION);
  }};
  return {Trigger:Trigger};
}());
pc.extend(pc, function() {
  var CollisionComponentSystem = function CollisionComponentSystem(app) {
    this.id = "collision";
    this.description = "Specifies a collision volume.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.CollisionComponent;
    this.DataType = pc.CollisionComponentData;
    this.schema = ["enabled", "type", "halfExtents", "radius", "axis", "height", "asset", "shape", "model"];
    this.implementations = {};
    this.on("remove", this.onRemove, this);
    pc.ComponentSystem.on("update", this.onUpdate, this);
  };
  CollisionComponentSystem = pc.inherits(CollisionComponentSystem, pc.ComponentSystem);
  CollisionComponentSystem.prototype = pc.extend(CollisionComponentSystem.prototype, {onLibraryLoaded:function() {
    if (typeof Ammo !== "undefined") {
    } else {
      pc.ComponentSystem.off("update", this.onUpdate, this);
    }
  }, initializeComponentData:function(component, _data, properties) {
    var data = {};
    properties = ["type", "halfExtents", "radius", "axis", "height", "shape", "model", "asset", "enabled"];
    properties.forEach(function(prop) {
      data[prop] = _data[prop];
    });
    if (_data.hasOwnProperty("asset")) {
      var idx = properties.indexOf("model");
      if (idx !== -1) {
        properties.splice(idx, 1);
      }
    } else {
      if (_data.hasOwnProperty("model")) {
        var idx = properties.indexOf("asset");
        if (idx !== -1) {
          properties.splice(idx, 1);
        }
      }
    }
    if (!data.type) {
      data.type = component.data.type;
    }
    component.data.type = data.type;
    if (data.halfExtents && pc.type(data.halfExtents) === "array") {
      data.halfExtents = new pc.Vec3(data.halfExtents[0], data.halfExtents[1], data.halfExtents[2]);
    }
    var impl = this._createImplementation(data.type);
    impl.beforeInitialize(component, data);
    CollisionComponentSystem._super.initializeComponentData.call(this.system, component, data, properties);
    impl.afterInitialize(component, data);
  }, _createImplementation:function(type) {
    if (this.implementations[type] === undefined) {
      var impl;
      switch(type) {
        case "box":
          impl = new CollisionBoxSystemImpl(this);
          break;
        case "sphere":
          impl = new CollisionSphereSystemImpl(this);
          break;
        case "capsule":
          impl = new CollisionCapsuleSystemImpl(this);
          break;
        case "cylinder":
          impl = new CollisionCylinderSystemImpl(this);
          break;
        case "mesh":
          impl = new CollisionMeshSystemImpl(this);
          break;
        default:
          throw "Invalid collision system type: " + type;
      }
      this.implementations[type] = impl;
    }
    return this.implementations[type];
  }, _getImplementation:function(entity) {
    return this.implementations[entity.collision.data.type];
  }, cloneComponent:function(entity, clone) {
    return this._getImplementation(entity).clone(entity, clone);
  }, onRemove:function(entity, data) {
    this.implementations[data.type].remove(entity, data);
  }, onUpdate:function(dt) {
    var id, entity, data;
    var components = this.store;
    for (id in components) {
      entity = components[id].entity;
      data = components[id].data;
      if (data.enabled && entity.enabled) {
        if (!entity.rigidbody && entity.trigger) {
          entity.trigger.syncEntityToBody();
        }
      }
    }
  }, onTransformChanged:function(component, position, rotation, scale) {
    this.implementations[component.data.type].updateTransform(component, position, rotation, scale);
  }, changeType:function(component, previousType, newType) {
    this.implementations[previousType].remove(component.entity, component.data);
    this._createImplementation(newType).reset(component, component.data);
  }, recreatePhysicalShapes:function(component) {
    this.implementations[component.data.type].recreatePhysicalShapes(component);
  }});
  var CollisionSystemImpl = function(system) {
    this.system = system;
  };
  CollisionSystemImpl.prototype = {beforeInitialize:function(component, data) {
    data.shape = this.createPhysicalShape(component.entity, data);
    data.model = new pc.Model;
    data.model.graph = new pc.GraphNode;
  }, afterInitialize:function(component, data) {
    this.recreatePhysicalShapes(component);
    component.data.initialized = true;
  }, reset:function(component, data) {
    this.beforeInitialize(component, data);
    this.afterInitialize(component, data);
  }, recreatePhysicalShapes:function(component) {
    var entity = component.entity;
    var data = component.data;
    if (typeof Ammo !== "undefined") {
      data.shape = this.createPhysicalShape(component.entity, data);
      if (entity.rigidbody) {
        entity.rigidbody.disableSimulation();
        entity.rigidbody.createBody();
      } else {
        if (!entity.trigger) {
          entity.trigger = new pc.Trigger(this.system.app, component, data);
        } else {
          entity.trigger.initialize(data);
        }
      }
    }
  }, createPhysicalShape:function(entity, data) {
    return undefined;
  }, updateTransform:function(component, position, rotation, scale) {
    if (component.entity.trigger) {
      component.entity.trigger.syncEntityToBody();
    }
  }, remove:function(entity, data) {
    var app = this.system.app;
    if (entity.rigidbody && entity.rigidbody.body) {
      app.systems.rigidbody.removeBody(entity.rigidbody.body);
      entity.rigidbody.disableSimulation();
    }
    if (entity.trigger) {
      entity.trigger.destroy();
      delete entity.trigger;
    }
    if (app.scene.containsModel(data.model)) {
      app.root.removeChild(data.model.graph);
      app.scene.removeModel(data.model);
    }
  }, clone:function(entity, clone) {
    var src = this.system.dataStore[entity._guid];
    var data = {enabled:src.data.enabled, type:src.data.type, halfExtents:[src.data.halfExtents.x, src.data.halfExtents.y, src.data.halfExtents.z], radius:src.data.radius, axis:src.data.axis, height:src.data.height, asset:src.data.asset, model:src.data.model};
    return this.system.addComponent(clone, data);
  }};
  var CollisionBoxSystemImpl = function(system) {
  };
  CollisionBoxSystemImpl = pc.inherits(CollisionBoxSystemImpl, CollisionSystemImpl);
  CollisionBoxSystemImpl.prototype = pc.extend(CollisionBoxSystemImpl.prototype, {createPhysicalShape:function(entity, data) {
    if (typeof Ammo !== "undefined") {
      var he = data.halfExtents;
      var ammoHe = new Ammo.btVector3(he.x, he.y, he.z);
      return new Ammo.btBoxShape(ammoHe);
    } else {
      return undefined;
    }
  }});
  var CollisionSphereSystemImpl = function(system) {
  };
  CollisionSphereSystemImpl = pc.inherits(CollisionSphereSystemImpl, CollisionSystemImpl);
  CollisionSphereSystemImpl.prototype = pc.extend(CollisionSphereSystemImpl.prototype, {createPhysicalShape:function(entity, data) {
    if (typeof Ammo !== "undefined") {
      return new Ammo.btSphereShape(data.radius);
    } else {
      return undefined;
    }
  }});
  var CollisionCapsuleSystemImpl = function(system) {
  };
  CollisionCapsuleSystemImpl = pc.inherits(CollisionCapsuleSystemImpl, CollisionSystemImpl);
  CollisionCapsuleSystemImpl.prototype = pc.extend(CollisionCapsuleSystemImpl.prototype, {createPhysicalShape:function(entity, data) {
    var shape = null;
    var axis = data.axis !== undefined ? data.axis : 1;
    var radius = data.radius || .5;
    var height = Math.max((data.height || 2) - 2 * radius, 0);
    if (typeof Ammo !== "undefined") {
      switch(axis) {
        case 0:
          shape = new Ammo.btCapsuleShapeX(radius, height);
          break;
        case 1:
          shape = new Ammo.btCapsuleShape(radius, height);
          break;
        case 2:
          shape = new Ammo.btCapsuleShapeZ(radius, height);
          break;
      }
    }
    return shape;
  }});
  var CollisionCylinderSystemImpl = function(system) {
  };
  CollisionCylinderSystemImpl = pc.inherits(CollisionCylinderSystemImpl, CollisionSystemImpl);
  CollisionCylinderSystemImpl.prototype = pc.extend(CollisionCylinderSystemImpl.prototype, {createPhysicalShape:function(entity, data) {
    var halfExtents = null;
    var shape = null;
    var axis = data.axis !== undefined ? data.axis : 1;
    var radius = data.radius !== undefined ? data.radius : .5;
    var height = data.height !== undefined ? data.height : 1;
    if (typeof Ammo !== "undefined") {
      switch(axis) {
        case 0:
          halfExtents = new Ammo.btVector3(height * .5, radius, radius);
          shape = new Ammo.btCylinderShapeX(halfExtents);
          break;
        case 1:
          halfExtents = new Ammo.btVector3(radius, height * .5, radius);
          shape = new Ammo.btCylinderShape(halfExtents);
          break;
        case 2:
          halfExtents = new Ammo.btVector3(radius, radius, height * .5);
          shape = new Ammo.btCylinderShapeZ(halfExtents);
          break;
      }
    }
    return shape;
  }});
  var CollisionMeshSystemImpl = function(system) {
  };
  CollisionMeshSystemImpl = pc.inherits(CollisionMeshSystemImpl, CollisionSystemImpl);
  CollisionMeshSystemImpl.prototype = pc.extend(CollisionMeshSystemImpl.prototype, {beforeInitialize:function(component, data) {
  }, createPhysicalShape:function(entity, data) {
    if (typeof Ammo !== "undefined" && data.model) {
      var model = data.model;
      var shape = new Ammo.btCompoundShape;
      var i, j;
      for (i = 0;i < model.meshInstances.length;i++) {
        var meshInstance = model.meshInstances[i];
        var mesh = meshInstance.mesh;
        var ib = mesh.indexBuffer[pc.RENDERSTYLE_SOLID];
        var vb = mesh.vertexBuffer;
        var format = vb.getFormat();
        var stride = format.size / 4;
        var positions;
        for (j = 0;j < format.elements.length;j++) {
          var element = format.elements[j];
          if (element.name === pc.SEMANTIC_POSITION) {
            positions = new Float32Array(vb.lock(), element.offset);
          }
        }
        var indices = new Uint16Array(ib.lock());
        var numTriangles = mesh.primitive[0].count / 3;
        var v1 = new Ammo.btVector3;
        var v2 = new Ammo.btVector3;
        var v3 = new Ammo.btVector3;
        var i1, i2, i3;
        var base = mesh.primitive[0].base;
        var triMesh = new Ammo.btTriangleMesh;
        for (j = 0;j < numTriangles;j++) {
          i1 = indices[base + j * 3] * stride;
          i2 = indices[base + j * 3 + 1] * stride;
          i3 = indices[base + j * 3 + 2] * stride;
          v1.setValue(positions[i1], positions[i1 + 1], positions[i1 + 2]);
          v2.setValue(positions[i2], positions[i2 + 1], positions[i2 + 2]);
          v3.setValue(positions[i3], positions[i3 + 1], positions[i3 + 2]);
          triMesh.addTriangle(v1, v2, v3, true);
        }
        var useQuantizedAabbCompression = true;
        var triMeshShape = new Ammo.btBvhTriangleMeshShape(triMesh, useQuantizedAabbCompression);
        var wtm = meshInstance.node.getWorldTransform();
        var scl = wtm.getScale();
        triMeshShape.setLocalScaling(new Ammo.btVector3(scl.x, scl.y, scl.z));
        var pos = meshInstance.node.getPosition();
        var rot = meshInstance.node.getRotation();
        var transform = new Ammo.btTransform;
        transform.setIdentity();
        transform.getOrigin().setValue(pos.x, pos.y, pos.z);
        var ammoQuat = new Ammo.btQuaternion;
        ammoQuat.setValue(rot.x, rot.y, rot.z, rot.w);
        transform.setRotation(ammoQuat);
        shape.addChildShape(transform, triMeshShape);
      }
      var entityTransform = entity.getWorldTransform();
      var scale = entityTransform.getScale();
      var vec = new Ammo.btVector3;
      vec.setValue(scale.x, scale.y, scale.z);
      shape.setLocalScaling(vec);
      return shape;
    } else {
      return undefined;
    }
  }, recreatePhysicalShapes:function(component) {
    var data = component.data;
    if (data.asset !== null && component.enabled && component.entity.enabled) {
      this.loadModelAsset(component);
    } else {
      this.doRecreatePhysicalShape(component);
    }
  }, loadModelAsset:function(component) {
    var self = this;
    var id = component.data.asset;
    var data = component.data;
    var assets = this.system.app.assets;
    var asset = assets.get(id);
    if (asset) {
      asset.ready(function(asset) {
        data.model = asset.resource;
        self.doRecreatePhysicalShape(component);
      });
      assets.load(asset);
    } else {
      assets.once("add:" + id, function(asset) {
        asset.ready(function(asset) {
          data.model = asset.resource;
          self.doRecreatePhysicalShape(component);
        });
        assets.load(asset);
      });
    }
  }, doRecreatePhysicalShape:function(component) {
    var entity = component.entity;
    var data = component.data;
    if (data.model) {
      if (data.shape) {
        Ammo.destroy(data.shape);
      }
      data.shape = this.createPhysicalShape(entity, data);
      if (entity.rigidbody) {
        entity.rigidbody.createBody();
      } else {
        if (!entity.trigger) {
          entity.trigger = new pc.Trigger(this.system.app, component, data);
        } else {
          entity.trigger.initialize(data);
        }
      }
    } else {
      this.remove(entity, data);
    }
  }, updateTransform:function(component, position, rotation, scale) {
    if (component.shape) {
      var entityTransform = component.entity.getWorldTransform();
      var worldScale = entityTransform.getScale();
      var previousScale = component.shape.getLocalScaling();
      if (worldScale.x !== previousScale.x() || worldScale.y !== previousScale.y() || worldScale.z !== previousScale.z()) {
        this.doRecreatePhysicalShape(component);
      }
    }
    CollisionMeshSystemImpl._super.updateTransform.call(this, component, position, rotation, scale);
  }});
  return {CollisionComponentSystem:CollisionComponentSystem};
}());
pc.extend(pc, function() {
  var CollisionComponent = function CollisionComponent(system, entity) {
    this.on("set_type", this.onSetType, this);
    this.on("set_halfExtents", this.onSetHalfExtents, this);
    this.on("set_radius", this.onSetRadius, this);
    this.on("set_height", this.onSetHeight, this);
    this.on("set_axis", this.onSetAxis, this);
    this.on("set_asset", this.onSetAsset, this);
    this.on("set_model", this.onSetModel, this);
  };
  CollisionComponent = pc.inherits(CollisionComponent, pc.Component);
  pc.extend(CollisionComponent.prototype, {onSetType:function(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      this.system.changeType(this, oldValue, newValue);
    }
  }, onSetHalfExtents:function(name, oldValue, newValue) {
    if (this.data.initialized && this.data.type === "box") {
      this.system.recreatePhysicalShapes(this);
    }
  }, onSetRadius:function(name, oldValue, newValue) {
    if (this.data.initialized && (this.data.type === "sphere" || this.data.type === "capsule" || this.data.type === "cylinder")) {
      this.system.recreatePhysicalShapes(this);
    }
  }, onSetHeight:function(name, oldValue, newValue) {
    if (this.data.initialized && (this.data.type === "capsule" || this.data.type === "cylinder")) {
      this.system.recreatePhysicalShapes(this);
    }
  }, onSetAxis:function(name, oldValue, newValue) {
    if (this.data.initialized && (this.data.type === "capsule" || this.data.type === "cylinder")) {
      this.system.recreatePhysicalShapes(this);
    }
  }, onSetAsset:function(name, oldValue, newValue) {
    var self = this;
    var asset;
    var assets = this.system.app.assets;
    if (oldValue) {
      asset = assets.get(oldValue);
      if (asset) {
        asset.off("remove", this.onAssetRemoved, this);
      }
    }
    if (newValue) {
      if (newValue instanceof pc.Asset) {
        this.data.asset = newValue.id;
      }
      asset = assets.get(this.data.asset);
      if (asset) {
        asset.off("remove", this.onAssetRemoved, this);
        asset.on("remove", this.onAssetRemoved, this);
      }
    }
    if (this.data.initialized && this.data.type === "mesh") {
      if (!newValue) {
        this.data.model = null;
      }
      this.system.recreatePhysicalShapes(this);
    }
  }, onSetModel:function(name, oldValue, newValue) {
    if (this.data.initialized && this.data.type === "mesh") {
      this.system.implementations.mesh.doRecreatePhysicalShape(this);
    }
  }, onAssetRemoved:function(asset) {
    asset.off("remove", this.onAssetRemoved, this);
    if (this.data.asset === asset.id) {
      this.asset = null;
    }
  }, onEnable:function() {
    CollisionComponent._super.onEnable.call(this);
    if (this.data.type === "mesh" && this.data.asset && this.data.initialized) {
      var asset = this.system.app.assets.get(this.data.asset);
      if (asset && (!asset.resource || !this.data.shape)) {
        this.system.recreatePhysicalShapes(this);
        return;
      }
    }
    if (this.entity.trigger) {
      this.entity.trigger.enable();
    } else {
      if (this.entity.rigidbody) {
        if (this.entity.rigidbody.enabled) {
          this.entity.rigidbody.enableSimulation();
        }
      }
    }
  }, onDisable:function() {
    CollisionComponent._super.onDisable.call(this);
    if (this.entity.trigger) {
      this.entity.trigger.disable();
    } else {
      if (this.entity.rigidbody) {
        this.entity.rigidbody.disableSimulation();
      }
    }
  }});
  return {CollisionComponent:CollisionComponent};
}());
pc.extend(pc, function() {
  var CollisionComponentData = function() {
    this.enabled = true;
    this.type = "box";
    this.halfExtents = new pc.Vec3(.5, .5, .5);
    this.radius = .5;
    this.axis = 1;
    this.height = 2;
    this.asset = null;
    this.shape = null;
    this.model = null;
    this.initialized = false;
  };
  CollisionComponentData = pc.inherits(CollisionComponentData, pc.ComponentData);
  return {CollisionComponentData:CollisionComponentData};
}());
pc.extend(pc, function() {
  var ParticleSystemComponentSystem = function ParticleSystemComponentSystem(app) {
    this.id = "particlesystem";
    this.description = "Updates and renders particle system in the scene.";
    app.systems.add(this.id, this);
    this.ComponentType = pc.ParticleSystemComponent;
    this.DataType = pc.ParticleSystemComponentData;
    this.schema = ["enabled", "autoPlay", "numParticles", "lifetime", "rate", "rate2", "startAngle", "startAngle2", "loop", "preWarm", "lighting", "halfLambert", "intensity", "depthWrite", "noFog", "depthSoftening", "sort", "blendType", "stretch", "alignToMotion", "emitterShape", "emitterExtents", "emitterRadius", "initialVelocity", "wrap", "wrapBounds", "localSpace", "colorMapAsset", "normalMapAsset", "mesh", "localVelocityGraph", "localVelocityGraph2", "velocityGraph", "velocityGraph2", "rotationSpeedGraph", 
    "rotationSpeedGraph2", "scaleGraph", "scaleGraph2", "colorGraph", "colorGraph2", "alphaGraph", "alphaGraph2", "colorMap", "normalMap", "animTilesX", "animTilesY", "animNumFrames", "animSpeed", "animLoop"];
    this.propertyTypes = {emitterExtents:"vec3", wrapBounds:"vec3", localVelocityGraph:"curveset", localVelocityGraph2:"curveset", velocityGraph:"curveset", velocityGraph2:"curveset", colorGraph:"curveset", colorGraph2:"curveset", alphaGraph:"curve", alphaGraph2:"curve", rotationSpeedGraph:"curve", rotationSpeedGraph2:"curve", scaleGraph:"curve", scaleGraph2:"curve"};
    this.on("beforeremove", this.onRemove, this);
    pc.ComponentSystem.on("update", this.onUpdate, this);
  };
  ParticleSystemComponentSystem = pc.inherits(ParticleSystemComponentSystem, pc.ComponentSystem);
  pc.extend(ParticleSystemComponentSystem.prototype, {initializeComponentData:function(component, _data, properties) {
    var data = {};
    properties = [];
    var types = this.propertyTypes;
    var type;
    for (var prop in _data) {
      if (_data.hasOwnProperty(prop)) {
        properties.push(prop);
        data[prop] = _data[prop];
      }
      if (types[prop] === "vec3") {
        if (pc.type(data[prop]) === "array") {
          data[prop] = new pc.Vec3(data[prop][0], data[prop][1], data[prop][2]);
        }
      } else {
        if (types[prop] === "curve") {
          if (!(data[prop] instanceof pc.Curve)) {
            type = data[prop].type;
            data[prop] = new pc.Curve(data[prop].keys);
            data[prop].type = type;
          }
        } else {
          if (types[prop] === "curveset") {
            if (!(data[prop] instanceof pc.CurveSet)) {
              type = data[prop].type;
              data[prop] = new pc.CurveSet(data[prop].keys);
              data[prop].type = type;
            }
          }
        }
      }
    }
    ParticleSystemComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, cloneComponent:function(entity, clone) {
    var source = entity.particlesystem.data;
    var schema = this.schema;
    var data = {};
    for (var i = 0, len = schema.length;i < len;i++) {
      var prop = schema[i];
      var sourceProp = source[prop];
      if (sourceProp instanceof pc.Vec3 || sourceProp instanceof pc.Curve || sourceProp instanceof pc.CurveSet) {
        sourceProp = sourceProp.clone();
        data[prop] = sourceProp;
      } else {
        if (sourceProp !== null && sourceProp !== undefined) {
          data[prop] = sourceProp;
        }
      }
    }
    return this.addComponent(clone, data);
  }, onUpdate:function(dt) {
    var components = this.store;
    var currentCamera;
    var numSteps, i;
    var stats = this.app.stats.particles;
    for (var id in components) {
      if (components.hasOwnProperty(id)) {
        var c = components[id];
        var entity = c.entity;
        var data = c.data;
        if (data.enabled && entity.enabled) {
          var emitter = data.model.emitter;
          if (!emitter.meshInstance.visible) {
            continue;
          }
          if (!data.paused) {
            emitter.simTime += dt;
            numSteps = 0;
            if (emitter.simTime > emitter.fixedTimeStep) {
              numSteps = Math.floor(emitter.simTime / emitter.fixedTimeStep);
              emitter.simTime -= numSteps * emitter.fixedTimeStep;
            }
            if (numSteps) {
              numSteps = Math.min(numSteps, emitter.maxSubSteps);
              for (i = 0;i < numSteps;i++) {
                emitter.addTime(emitter.fixedTimeStep);
              }
              stats._updatesPerFrame += numSteps;
              stats._frameTime += emitter._addTimeTime;
              emitter._addTimeTime = 0;
            }
            emitter.finishFrame();
          }
        }
      }
    }
  }, onRemove:function(entity, component) {
    var data = component.data;
    if (data.model) {
      this.app.scene.removeModel(data.model);
      entity.removeChild(data.model.getGraph());
      data.model = null;
    }
    if (component.emitter) {
      component.emitter.destroy();
      component.emitter = null;
    }
  }});
  return {ParticleSystemComponentSystem:ParticleSystemComponentSystem};
}());
pc.extend(pc, function() {
  var SIMPLE_PROPERTIES = ["emitterExtents", "emitterRadius", "loop", "initialVelocity", "animSpeed", "normalMap"];
  var COMPLEX_PROPERTIES = ["numParticles", "lifetime", "rate", "rate2", "startAngle", "startAngle2", "lighting", "halfLambert", "intensity", "wrap", "wrapBounds", "depthWrite", "noFog", "sort", "stretch", "alignToMotion", "preWarm", "emitterShape", "animTilesX", "animTilesY", "animFrames", "animLoop", "colorMap", "localSpace"];
  var GRAPH_PROPERTIES = ["scaleGraph", "scaleGraph2", "colorGraph", "colorGraph2", "alphaGraph", "alphaGraph2", "velocityGraph", "velocityGraph2", "localVelocityGraph", "localVelocityGraph2", "rotationSpeedGraph", "rotationSpeedGraph2"];
  var ASSET_PROPERTIES = ["colorMapAsset", "normalMapAsset", "mesh"];
  var ParticleSystemComponent = function ParticleSystemComponent(system, entity) {
    this.on("set_colorMapAsset", this.onSetColorMapAsset, this);
    this.on("set_normalMapAsset", this.onSetNormalMapAsset, this);
    this.on("set_mesh", this.onSetMesh, this);
    this.on("set_loop", this.onSetLoop, this);
    this.on("set_blendType", this.onSetBlendType, this);
    this.on("set_depthSoftening", this.onSetDepthSoftening, this);
    SIMPLE_PROPERTIES.forEach(function(prop) {
      this.on("set_" + prop, this.onSetSimpleProperty, this);
    }.bind(this));
    COMPLEX_PROPERTIES.forEach(function(prop) {
      this.on("set_" + prop, this.onSetComplexProperty, this);
    }.bind(this));
    GRAPH_PROPERTIES.forEach(function(prop) {
      this.on("set_" + prop, this.onSetGraphProperty, this);
    }.bind(this));
  };
  ParticleSystemComponent = pc.inherits(ParticleSystemComponent, pc.Component);
  pc.extend(ParticleSystemComponent.prototype, {onSetColorMapAsset:function(name, oldValue, newValue) {
    var self = this;
    var asset;
    var assets = this.system.app.assets;
    if (oldValue) {
      asset = assets.get(oldValue);
      if (asset) {
        asset.off("remove", this.onColorMapRemoved, this);
      }
    }
    if (newValue) {
      if (newValue instanceof pc.Asset) {
        this.data.colorMapAsset = newValue.id;
        newValue = newValue.id;
      }
      asset = assets.get(newValue);
      if (asset) {
        asset.on("remove", this.onColorMapRemoved, this);
        asset.ready(function(asset) {
          self.colorMap = asset.resource;
        });
        if (self.enabled && self.entity.enabled) {
          assets.load(asset);
        }
      } else {
        assets.once("add:" + newValue, function(asset) {
          asset.on("remove", this.onColorMapRemoved, this);
          asset.ready(function(asset) {
            self.colorMap = asset.resource;
          });
          if (self.enabled && self.entity.enabled) {
            assets.load(asset);
          }
        });
      }
    } else {
      this.colorMap = null;
    }
  }, onColorMapRemoved:function(asset) {
    asset.off("remove", this.onColorMapRemoved, this);
    this.colorMapAsset = null;
  }, onSetNormalMapAsset:function(name, oldValue, newValue) {
    var self = this;
    var asset;
    var assets = this.system.app.assets;
    if (oldValue) {
      asset = assets.get(oldValue);
      if (asset) {
        asset.off("remove", this.onNormalMapRemoved, this);
      }
    }
    if (newValue) {
      if (newValue instanceof pc.Asset) {
        this.data.normalMapAsset = newValue.id;
        newValue = newValue.id;
      }
      asset = assets.get(newValue);
      if (asset) {
        asset.on("remove", this.onNormalMapRemoved, this);
        asset.ready(function(asset) {
          self.normalMap = asset.resource;
        });
        if (self.enabled && self.entity.enabled) {
          assets.load(asset);
        }
      } else {
        assets.once("add:" + newValue, function(asset) {
          asset.on("remove", this.onNormalMapRemoved, this);
          asset.ready(function(asset) {
            self.normalMap = asset.resource;
          });
          if (self.enabled && self.entity.enabled) {
            assets.load(asset);
          }
        });
      }
    } else {
      this.normalMap = null;
    }
  }, onNormalMapRemoved:function(asset) {
    asset.off("remove", this.onNormalMapRemoved, this);
    this.normalMapAsset = null;
  }, onSetMesh:function(name, oldValue, newValue) {
    var self = this;
    var asset;
    var assets = this.system.app.assets;
    if (oldValue && typeof oldValue === "number") {
      asset = assets.get(oldValue);
      if (asset) {
        asset.off("remove", this.onMeshRemoved, this);
      }
    }
    if (newValue) {
      if (newValue instanceof pc.Asset) {
        this.data.mesh = newValue.id;
        newValue = newValue.id;
      }
      if (typeof newValue === "number") {
        asset = assets.get(newValue);
        if (asset) {
          asset.on("remove", this.onMeshRemoved, this);
          asset.ready(function(asset) {
            self._onMeshChanged(asset.resource);
          });
          if (self.enabled && self.entity.enabled) {
            assets.load(asset);
          }
        } else {
          assets.once("add:" + newValue, function(asset) {
            asset.on("remove", this.onMeshRemoved, this);
            asset.ready(function(asset) {
              self._onMeshChanged(asset.resource);
            });
            if (self.enabled && self.entity.enabled) {
              assets.load(asset);
            }
          });
        }
      } else {
        this._onMeshChanged(newValue);
      }
    } else {
      this._onMeshChanged(null);
    }
  }, _onMeshChanged:function(mesh) {
    if (mesh && !(mesh instanceof pc.Mesh)) {
      if (mesh.meshInstances[0]) {
        mesh = mesh.meshInstances[0].mesh;
      } else {
        mesh = null;
      }
    }
    this.data.mesh = mesh;
    if (this.emitter) {
      this.emitter.mesh = mesh;
      this.emitter.resetMaterial();
      this.rebuild();
    }
  }, onMeshRemoved:function(asset) {
    asset.off("remove", this.onMeshRemoved, this);
    this.mesh = null;
  }, onSetLoop:function(name, oldValue, newValue) {
    if (this.emitter) {
      this.emitter[name] = newValue;
      this.emitter.resetTime();
    }
  }, onSetBlendType:function(name, oldValue, newValue) {
    if (this.emitter) {
      this.emitter[name] = newValue;
      this.emitter.material.blendType = newValue;
      this.emitter.resetMaterial();
      this.rebuild();
    }
  }, onSetDepthSoftening:function(name, oldValue, newValue) {
    if (this.emitter) {
      if (oldValue !== newValue) {
        if (newValue) {
          this.emitter[name] = newValue;
          if (this.enabled) {
            this.emitter.onEnableDepth();
          }
        } else {
          if (this.enabled) {
            this.emitter.onDisableDepth();
          }
          this.emitter[name] = newValue;
        }
        this.reset();
        this.emitter.resetMaterial();
        this.rebuild();
      }
    }
  }, onSetSimpleProperty:function(name, oldValue, newValue) {
    if (this.emitter) {
      this.emitter[name] = newValue;
      this.emitter.resetMaterial();
    }
  }, onSetComplexProperty:function(name, oldValue, newValue) {
    if (this.emitter) {
      this.emitter[name] = newValue;
      this.reset();
      this.emitter.resetMaterial();
      this.rebuild();
    }
  }, onSetGraphProperty:function(name, oldValue, newValue) {
    if (this.emitter) {
      this.emitter[name] = newValue;
      this.emitter.rebuildGraphs();
      this.emitter.resetMaterial();
    }
  }, onEnable:function() {
    for (var i = 0, len = ASSET_PROPERTIES.length;i < len;i++) {
      var asset = this.data[ASSET_PROPERTIES[i]];
      if (asset) {
        if (!(asset instanceof pc.Asset)) {
          var id = parseInt(asset, 10);
          if (id >= 0) {
            asset = this.system.app.assets.get(asset);
          } else {
            continue;
          }
        }
        if (asset && !asset.resource) {
          this.system.app.assets.load(asset);
        }
      }
    }
    var firstRun = false;
    if (!this.emitter) {
      var mesh = this.data.mesh;
      if (!(mesh instanceof pc.Mesh)) {
        mesh = null;
      }
      firstRun = true;
      this.emitter = new pc.ParticleEmitter(this.system.app.graphicsDevice, {numParticles:this.data.numParticles, emitterExtents:this.data.emitterExtents, emitterRadius:this.data.emitterRadius, emitterShape:this.data.emitterShape, initialVelocity:this.data.initialVelocity, wrap:this.data.wrap, localSpace:this.data.localSpace, wrapBounds:this.data.wrapBounds, lifetime:this.data.lifetime, rate:this.data.rate, rate2:this.data.rate2, animTilesX:this.data.animTilesX, animTilesY:this.data.animTilesY, animNumFrames:this.data.animNumFrames, 
      animSpeed:this.data.animSpeed, animLoop:this.data.animLoop, startAngle:this.data.startAngle, startAngle2:this.data.startAngle2, scaleGraph:this.data.scaleGraph, scaleGraph2:this.data.scaleGraph2, colorGraph:this.data.colorGraph, colorGraph2:this.data.colorGraph2, alphaGraph:this.data.alphaGraph, alphaGraph2:this.data.alphaGraph2, localVelocityGraph:this.data.localVelocityGraph, localVelocityGraph2:this.data.localVelocityGraph2, velocityGraph:this.data.velocityGraph, velocityGraph2:this.data.velocityGraph2, 
      rotationSpeedGraph:this.data.rotationSpeedGraph, rotationSpeedGraph2:this.data.rotationSpeedGraph2, colorMap:this.data.colorMap, normalMap:this.data.normalMap, loop:this.data.loop, preWarm:this.data.preWarm, sort:this.data.sort, stretch:this.data.stretch, alignToMotion:this.data.alignToMotion, lighting:this.data.lighting, halfLambert:this.data.halfLambert, intensity:this.data.intensity, depthSoftening:this.data.depthSoftening, scene:this.system.app.scene, mesh:mesh, depthWrite:this.data.depthWrite, 
      noFog:this.data.noFog, node:this.entity, blendType:this.data.blendType});
      this.emitter.meshInstance.node = this.entity;
      this.psys = new pc.Model;
      this.psys.graph = this.entity;
      this.psys.emitter = this.emitter;
      this.psys.meshInstances = [this.emitter.meshInstance];
      this.data.model = this.psys;
      this.emitter.psys = this.psys;
      if (!this.data.autoPlay) {
        this.pause();
        this.emitter.meshInstance.visible = false;
      }
    }
    if (this.data.model) {
      if (!this.system.app.scene.containsModel(this.data.model)) {
        if (this.emitter.colorMap) {
          this.system.app.scene.addModel(this.data.model);
          if (!firstRun) {
            this.emitter.onEnableDepth();
          }
        }
      }
    }
    ParticleSystemComponent._super.onEnable.call(this);
  }, onDisable:function() {
    ParticleSystemComponent._super.onDisable.call(this);
    if (this.data.model) {
      if (this.system.app.scene.containsModel(this.data.model)) {
        this.system.app.scene.removeModel(this.data.model);
        this.emitter.onDisableDepth();
      }
    }
  }, reset:function() {
    if (this.emitter) {
      this.emitter.reset();
    }
  }, stop:function() {
    if (this.emitter) {
      this.emitter.loop = false;
      this.emitter.resetTime();
      this.emitter.addTime(0, true);
    }
  }, pause:function() {
    this.data.paused = true;
  }, unpause:function() {
    this.data.paused = false;
  }, play:function() {
    this.data.paused = false;
    if (this.emitter) {
      this.emitter.meshInstance.visible = true;
      this.emitter.loop = this.data.loop;
      this.emitter.resetTime();
    }
  }, isPlaying:function() {
    if (this.data.paused) {
      return false;
    } else {
      if (this.emitter && this.emitter.loop) {
        return true;
      } else {
        return Date.now() <= this.emitter.endTime;
      }
    }
  }, rebuild:function() {
    var enabled = this.enabled;
    this.enabled = false;
    if (this.emitter) {
      this.emitter.rebuild();
      this.emitter.meshInstance.node = this.entity;
      this.data.model.meshInstances = [this.emitter.meshInstance];
    }
    this.enabled = enabled;
  }});
  return {ParticleSystemComponent:ParticleSystemComponent};
}());
pc.extend(pc, function() {
  var ParticleSystemComponentData = function() {
    this.numParticles = 1;
    this.rate = 1;
    this.rate2 = null;
    this.startAngle = 0;
    this.startAngle2 = null;
    this.lifetime = 50;
    this.emitterExtents = new pc.Vec3;
    this.emitterRadius = 0;
    this.emitterShape = pc.EMITTERSHAPE_BOX;
    this.initialVelocity = 0;
    this.wrapBounds = new pc.Vec3;
    this.localSpace = false;
    this.colorMap = null;
    this.colorMapAsset = null;
    this.normalMap = null;
    this.normalMapAsset = null;
    this.loop = true;
    this.preWarm = false;
    this.sort = 0;
    this.mode = pc.PARTICLEMODE_GPU;
    this.scene = null;
    this.lighting = false;
    this.halfLambert = false;
    this.intensity = 1;
    this.stretch = 0;
    this.alignToMotion = false;
    this.depthSoftening = 0;
    this.mesh = null;
    this.depthWrite = false;
    this.noFog = false;
    this.animTilesX = 1;
    this.animTilesY = 1;
    this.animNumFrames = 1;
    this.animSpeed = 1;
    this.animLoop = true;
    this.scaleGraph = null;
    this.scaleGraph2 = null;
    this.colorGraph = null;
    this.colorGraph2 = null;
    this.alphaGraph = null;
    this.alphaGraph2 = null;
    this.localVelocityGraph = null;
    this.localVelocityGraph2 = null;
    this.velocityGraph = null;
    this.velocityGraph2 = null;
    this.rotationSpeedGraph = null;
    this.rotationSpeedGraph2 = null;
    this.blendType = pc.BLEND_NORMAL;
    this.model = null;
    this.enabled = true;
    this.paused = false;
    this.autoPlay = true;
  };
  ParticleSystemComponentData = pc.inherits(ParticleSystemComponentData, pc.ComponentData);
  return {ParticleSystemComponentData:ParticleSystemComponentData};
}());
pc.extend(pc, function() {
  var ScreenComponentSystem = function ScreenComponentSystem(app) {
    this.id = "screen";
    this.app = app;
    app.systems.add(this.id, this);
    this.ComponentType = pc.ScreenComponent;
    this.DataType = pc.ScreenComponentData;
    this.schema = ["enabled"];
    this.windowResolution = new pc.Vec2;
    this.app.graphicsDevice.on("resizecanvas", this._onResize, this);
    pc.ComponentSystem.on("update", this._onUpdate, this);
  };
  ScreenComponentSystem = pc.inherits(ScreenComponentSystem, pc.ComponentSystem);
  pc.extend(ScreenComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    if (data.screenSpace !== undefined) {
      component.screenSpace = data.screenSpace;
    }
    if (data.scaleMode !== undefined) {
      component.scaleMode = data.scaleMode;
    }
    if (data.scaleBlend !== undefined) {
      component.scaleBlend = data.scaleBlend;
    }
    if (data.resolution !== undefined) {
      if (data.resolution instanceof pc.Vec2) {
        component._resolution.copy(data.resolution);
      } else {
        component._resolution.set(data.resolution[0], data.resolution[1]);
      }
      component.resolution = component._resolution;
    }
    if (data.referenceResolution !== undefined) {
      if (data.referenceResolution instanceof pc.Vec2) {
        component._referenceResolution.copy(data.referenceResolution);
      } else {
        component._referenceResolution.set(data.referenceResolution[0], data.referenceResolution[1]);
      }
      component.referenceResolution = component._referenceResolution;
    }
    ScreenComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, _onUpdate:function(dt) {
    var components = this.store;
    for (var id in components) {
      if (components[id].entity.screen.update) {
        components[id].entity.screen.update(dt);
      }
    }
  }, _onResize:function(width, height) {
    this.windowResolution.x = width;
    this.windowResolution.y = height;
  }, cloneComponent:function(entity, clone) {
    var screen = entity.screen;
    return this.addComponent(clone, {enabled:screen.enabled, screenSpace:screen.screenSpace, scaleMode:screen.scaleMode, resolution:screen.resolution.clone(), referenceResolution:screen.referenceResolution.clone()});
  }});
  return {ScreenComponentSystem:ScreenComponentSystem};
}());
pc.extend(pc, function() {
  var ScreenComponent = function ScreenComponent(system, entity) {
    this._resolution = new pc.Vec2(640, 320);
    this._referenceResolution = new pc.Vec2(640, 320);
    this._scaleMode = pc.ScreenComponent.SCALEMODE_NONE;
    this.scale = 1;
    this._scaleBlend = .5;
    this._screenSpace = false;
    this._screenMatrix = new pc.Mat4;
    system.app.graphicsDevice.on("resizecanvas", this._onResize, this);
  };
  ScreenComponent = pc.inherits(ScreenComponent, pc.Component);
  ScreenComponent.SCALEMODE_NONE = "none";
  ScreenComponent.SCALEMODE_BLEND = "blend";
  var _transform = new pc.Mat4;
  pc.extend(ScreenComponent.prototype, {update:function(dt) {
  }, syncDrawOrder:function() {
    var i = 1;
    var recurse = function(e) {
      if (e.element) {
        e.element.drawOrder = i++;
      }
      var children = e.getChildren();
      for (var j = 0;j < children.length;j++) {
        recurse(children[j]);
      }
    };
    recurse(this.entity);
  }, _calcProjectionMatrix:function() {
    var left;
    var right;
    var bottom;
    var top;
    var near = 1;
    var far = -1;
    var w = this._resolution.x * this.scale;
    var h = this._resolution.y * this.scale;
    left = 0;
    right = w;
    bottom = -h;
    top = 0;
    this._screenMatrix.setOrtho(left, right, bottom, top, near, far);
    if (!this._screenSpace) {
      _transform.setScale(.5 * w, .5 * h, 1);
      this._screenMatrix.mul2(_transform, this._screenMatrix);
    }
  }, _onResize:function(width, height) {
    if (this._screenSpace) {
      this._resolution.set(width, height);
      this.resolution = this._resolution;
    }
  }});
  Object.defineProperty(ScreenComponent.prototype, "resolution", {set:function(value) {
    if (!this._screenSpace) {
      this._resolution.set(value.x, value.y);
    } else {
      this._resolution.set(this.system.app.graphicsDevice.width, this.system.app.graphicsDevice.height);
    }
    var refRes = this.referenceResolution;
    this._scalex = refRes.x / this._resolution.x;
    this._scaley = refRes.y / this._resolution.y;
    this.scale = this._scalex * (1 - this._scaleBlend) + this._scaley * this._scaleBlend;
    this._calcProjectionMatrix();
    this.fire("set:resolution", this._resolution);
  }, get:function() {
    return this._resolution;
  }});
  Object.defineProperty(ScreenComponent.prototype, "referenceResolution", {set:function(value) {
    this._referenceResolution.set(value.x, value.y);
    var refRes = this.referenceResolution;
    this._scalex = refRes.x / this._resolution.x;
    this._scaley = refRes.y / this._resolution.y;
    this.scale = this._scalex * (1 - this._scaleBlend) + this._scaley * this._scaleBlend;
    this._calcProjectionMatrix();
    this.fire("set:referenceresolution", this._resolution);
  }, get:function() {
    if (this._scaleMode === pc.ScreenComponent.SCALEMODE_NONE) {
      return this._resolution;
    } else {
      return this._referenceResolution;
    }
  }});
  Object.defineProperty(ScreenComponent.prototype, "screenSpace", {set:function(value) {
    this._screenSpace = value;
    if (this._screenSpace) {
      this._resolution.set(this.system.app.graphicsDevice.width, this.system.app.graphicsDevice.height);
    }
    this.resolution = this._resolution;
    this.fire("set:screenspace", this._screenSpace);
  }, get:function() {
    return this._screenSpace;
  }});
  Object.defineProperty(ScreenComponent.prototype, "scaleMode", {set:function(value) {
    if (value !== pc.ScreenComponent.SCALEMODE_NONE && value !== pc.ScreenComponent.SCALEMODE_BLEND) {
      value = pc.ScreenComponent.SCALEMODE_NONE;
    }
    this._scaleMode = value;
    this.resolution = this._resolution;
    this.fire("set:scalemode", this._scaleMode);
  }, get:function() {
    return this._scaleMode;
  }});
  Object.defineProperty(ScreenComponent.prototype, "scaleBlend", {set:function(value) {
    this._scaleBlend = value;
    this._scalex = this._referenceResolution.x / this._resolution.x;
    this._scaley = this._referenceResolution.y / this._resolution.y;
    this.scale = this._scalex * (1 - this._scaleBlend) + this._scaley * this._scaleBlend;
    this._calcProjectionMatrix();
    this.fire("set:scaleblend", this._scaleBlend);
  }, get:function() {
    return this._scaleBlend;
  }});
  return {ScreenComponent:ScreenComponent};
}());
pc.extend(pc, function() {
  var ScreenComponentData = function() {
    this.enabled = true;
  };
  ScreenComponentData = pc.inherits(ScreenComponentData, pc.ComponentData);
  return {ScreenComponentData:ScreenComponentData};
}());
pc.extend(pc, function() {
  var ElementComponentSystem = function ElementComponentSystem(app) {
    this.id = "element";
    this.app = app;
    app.systems.add(this.id, this);
    this.ComponentType = pc.ElementComponent;
    this.DataType = pc.ElementComponentData;
    this.schema = ["enabled"];
    this._defaultTexture = new pc.Texture(app.graphicsDevice, {width:4, height:4, format:pc.PIXELFORMAT_R8_G8_B8});
    this.defaultImageMaterial = new pc.StandardMaterial;
    this.defaultImageMaterial.emissive = new pc.Color(.5, .5, .5, 1);
    this.defaultImageMaterial.emissiveMap = this._defaultTexture;
    this.defaultImageMaterial.emissiveMapTint = true;
    this.defaultImageMaterial.opacityMap = this._defaultTexture;
    this.defaultImageMaterial.opacityMapChannel = "a";
    this.defaultImageMaterial.opacityTint = true;
    this.defaultImageMaterial.opacity = 0;
    this.defaultImageMaterial.useLighting = false;
    this.defaultImageMaterial.useGammaTonemap = false;
    this.defaultImageMaterial.useFog = false;
    this.defaultImageMaterial.useSkybox = false;
    this.defaultImageMaterial.blendType = pc.BLEND_PREMULTIPLIED;
    this.defaultImageMaterial.depthWrite = false;
    this.defaultImageMaterial.update();
    this.defaultScreenSpaceImageMaterial = new pc.StandardMaterial;
    this.defaultScreenSpaceImageMaterial.emissive = new pc.Color(.5, .5, .5, 1);
    this.defaultScreenSpaceImageMaterial.emissiveMap = this._defaultTexture;
    this.defaultScreenSpaceImageMaterial.emissiveMapTint = true;
    this.defaultScreenSpaceImageMaterial.opacityMap = this._defaultTexture;
    this.defaultScreenSpaceImageMaterial.opacityMapChannel = "a";
    this.defaultScreenSpaceImageMaterial.opacityTint = true;
    this.defaultScreenSpaceImageMaterial.opacity = 0;
    this.defaultScreenSpaceImageMaterial.useLighting = false;
    this.defaultScreenSpaceImageMaterial.useGammaTonemap = false;
    this.defaultScreenSpaceImageMaterial.useFog = false;
    this.defaultScreenSpaceImageMaterial.useSkybox = false;
    this.defaultScreenSpaceImageMaterial.blendType = pc.BLEND_PREMULTIPLIED;
    this.defaultScreenSpaceImageMaterial.depthTest = false;
    this.defaultScreenSpaceImageMaterial.depthWrite = false;
    this.defaultScreenSpaceImageMaterial.update();
    this.defaultTextMaterial = new pc.StandardMaterial;
    this.defaultTextMaterial.msdfMap = this._defaultTexture;
    this.defaultTextMaterial.useLighting = false;
    this.defaultTextMaterial.useGammaTonemap = false;
    this.defaultTextMaterial.useFog = false;
    this.defaultTextMaterial.useSkybox = false;
    this.defaultTextMaterial.emissive = new pc.Color(1, 1, 1, 1);
    this.defaultTextMaterial.opacity = .5;
    this.defaultTextMaterial.blendType = pc.BLEND_PREMULTIPLIED;
    this.defaultTextMaterial.depthWrite = false;
    this.defaultTextMaterial.update();
    this.defaultScreenSpaceTextMaterial = new pc.StandardMaterial;
    this.defaultScreenSpaceTextMaterial.msdfMap = this._defaultTexture;
    this.defaultScreenSpaceTextMaterial.useLighting = false;
    this.defaultScreenSpaceTextMaterial.useGammaTonemap = false;
    this.defaultScreenSpaceTextMaterial.useFog = false;
    this.defaultScreenSpaceTextMaterial.useSkybox = false;
    this.defaultScreenSpaceTextMaterial.emissive = new pc.Color(1, 1, 1, 1);
    this.defaultScreenSpaceTextMaterial.opacity = .5;
    this.defaultScreenSpaceTextMaterial.blendType = pc.BLEND_PREMULTIPLIED;
    this.defaultScreenSpaceTextMaterial.depthWrite = false;
    this.defaultScreenSpaceTextMaterial.depthTest = false;
    this.defaultScreenSpaceTextMaterial.update();
    this.on("beforeremove", this.onRemoveComponent, this);
  };
  ElementComponentSystem = pc.inherits(ElementComponentSystem, pc.ComponentSystem);
  pc.extend(ElementComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    if (data.width !== undefined) {
      component.width = data.width;
    }
    if (data.height !== undefined) {
      component.height = data.height;
    }
    if (data.anchor !== undefined) {
      if (data.anchor instanceof pc.Vec4) {
        component.anchor.copy(data.anchor);
      } else {
        component.anchor.set(data.anchor[0], data.anchor[1], data.anchor[2], data.anchor[3]);
      }
    }
    if (data.pivot !== undefined) {
      if (data.pivot instanceof pc.Vec2) {
        component.pivot.copy(data.pivot);
      } else {
        component.pivot.set(data.pivot[0], data.pivot[1]);
      }
    }
    component.type = data.type;
    if (component.type === pc.ELEMENTTYPE_IMAGE) {
      if (data.rect !== undefined) {
        if (data.rect instanceof pc.Vec4) {
          component.rect.copy(data.rect);
        } else {
          component.rect.set(data.rect[0], data.rect[1], data.rect[2], data.rect[3]);
        }
      }
      if (data.materialAsset !== undefined) {
        component.materialAsset = data.materialAsset;
      }
      if (data.material) {
        component.material = data.material;
      }
      if (data.color !== undefined) {
        if (data.color instanceof pc.Color) {
          component.color.set(data.color.data[0], data.color.data[1], data.color.data[2], data.opacity !== undefined ? data.opacity : 1);
        } else {
          component.color.set(data.color[0], data.color[1], data.color[2], data.opacity !== undefined ? data.opacity : 1);
        }
      } else {
        if (data.opacity !== undefined) {
          component.opacity = data.opacity;
        }
      }
      if (data.textureAsset !== undefined) {
        component.textureAsset = data.textureAsset;
      }
      if (data.texture) {
        component.texture = data.texture;
      }
    } else {
      if (component.type === pc.ELEMENTTYPE_TEXT) {
        if (data.text !== undefined) {
          component.text = data.text;
        }
        if (data.color !== undefined) {
          if (data.color instanceof pc.Color) {
            component.color.set(data.color.data[0], data.color.data[1], data.color.data[2], data.opacity !== undefined ? data.opacity : 1);
          } else {
            component.color.set(data.color[0], data.color[1], data.color[2], data.opacity !== undefined ? data.opacity : 1);
          }
        } else {
          if (data.opacity !== undefined) {
            component.opacity = data.opacity;
          }
        }
        if (data.spacing !== undefined) {
          component.spacing = data.spacing;
        }
        if (data.fontSize !== undefined) {
          component.fontSize = data.fontSize;
          if (!data.lineHeight) {
            component.lineHeight = data.fontSize;
          }
        }
        if (data.lineHeight !== undefined) {
          component.lineHeight = data.lineHeight;
        }
        if (data.fontAsset !== undefined) {
          component.fontAsset = data.fontAsset;
        }
        if (data.font !== undefined) {
          component.font = data.font;
        }
      } else {
      }
    }
    var screen = component._findScreen();
    if (screen) {
      component._updateScreen(screen);
    }
    ElementComponentSystem._super.initializeComponentData.call(this, component, data, properties);
  }, onRemoveComponent:function(entity, component) {
    component.onRemove();
  }, cloneComponent:function(entity, clone) {
    var source = entity.element;
    return this.addComponent(clone, {enabled:source.enabled, width:source.width, height:source.height, anchor:source.anchor.clone(), pivot:source.pivot.clone(), type:source.type, rect:source.rect && source.rect.clone() || source.rect, materialAsset:source.materialAsset, material:source.material, color:source.color.clone(), opacity:source.opacity, textureAsset:source.textureAsset, texture:source.texture, text:source.text, spacing:source.spacing, lineHeight:source.lineHeight, fontSize:source.fontSize, 
    fontAsset:source.fontAsset, font:source.font});
  }});
  return {ElementComponentSystem:ElementComponentSystem};
}());
pc.extend(pc, function() {
  pc.ELEMENTTYPE_GROUP = "group";
  pc.ELEMENTTYPE_IMAGE = "image";
  pc.ELEMENTTYPE_TEXT = "text";
  var _warning = false;
  var ElementComponent = function ElementComponent(system, entity) {
    this._anchor = new pc.Vec4;
    this._worldAnchor = new pc.Vec4;
    this._pivot = new pc.Vec2;
    this._width = 32;
    this._height = 32;
    this._modelTransform = new pc.Mat4;
    this._screenToWorld = new pc.Mat4;
    this._canvasPosition = new pc.Vec2;
    this._anchorTransform = new pc.Mat4;
    this._anchorDirty = true;
    this.entity.on("insert", this._onInsert, this);
    this.screen = null;
    this._type = pc.ELEMENTTYPE_GROUP;
    this._image = null;
    this._text = null;
    this._group = null;
    if (!_warning) {
      console.warn("Message from PlayCanvas: The element component is currently in Beta. APIs may change without notice.");
      _warning = true;
    }
  };
  ElementComponent = pc.inherits(ElementComponent, pc.Component);
  pc.extend(ElementComponent.prototype, {_patch:function() {
    this.entity.sync = this._sync;
    this.entity.setPosition = this._setPosition;
  }, _unpatch:function() {
    this.entity.sync = pc.Entity.prototype.sync;
    this.entity.setPosition = pc.Entity.prototype.setPosition;
  }, _setPosition:function() {
    var position = new pc.Vec3;
    var invParentWtm = new pc.Mat4;
    return function(x, y, z) {
      if (x instanceof pc.Vec3) {
        position.copy(x);
      } else {
        position.set(x, y, z);
      }
      this.getWorldTransform();
      invParentWtm.copy(this.element._screenToWorld).invert();
      invParentWtm.transformPoint(position, this.localPosition);
      this.dirtyLocal = true;
    };
  }(), _sync:function() {
    if (this.dirtyLocal) {
      this.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);
      this.dirtyLocal = false;
      this.dirtyWorld = true;
      this._aabbVer++;
    }
    var resx = 0;
    var resy = 0;
    var screen = this.element.screen;
    if (this.element._anchorDirty) {
      var px = 0;
      var py = 1;
      if (this._parent && this._parent.element) {
        resx = this._parent.element.width;
        resy = this._parent.element.height;
        px = this._parent.element.pivot.x;
        py = this._parent.element.pivot.y;
      } else {
        if (screen) {
          var resolution = screen.screen.resolution;
          resx = resolution.x * screen.screen.scale;
          resy = resolution.y * screen.screen.scale;
        }
      }
      this.element._anchorTransform.setTranslate(resx * (this.element.anchor.x - px), -(resy * (py - this.element.anchor.y)), 0);
      this.element._anchorDirty = false;
    }
    if (this.dirtyWorld) {
      if (this._parent === null) {
        this.worldTransform.copy(this.localTransform);
      } else {
        if (this._parent.element) {
          this.element._screenToWorld.mul2(this._parent.element._modelTransform, this.element._anchorTransform);
        } else {
          this.element._screenToWorld.copy(this.element._anchorTransform);
        }
        this.element._modelTransform.mul2(this.element._screenToWorld, this.localTransform);
        if (screen) {
          this.element._screenToWorld.mul2(screen.screen._screenMatrix, this.element._screenToWorld);
          if (!screen.screen.screenSpace) {
            this.element._screenToWorld.mul2(screen.worldTransform, this.element._screenToWorld);
          }
          this.worldTransform.mul2(this.element._screenToWorld, this.localTransform);
        } else {
          this.worldTransform.copy(this.element._modelTransform);
        }
      }
      this.dirtyWorld = false;
      var child;
      for (var i = 0, len = this._children.length;i < len;i++) {
        child = this._children[i];
        child.dirtyWorld = true;
        child._aabbVer++;
      }
    }
  }, _onInsert:function(parent) {
    var screen = this._findScreen();
    this._updateScreen(screen);
  }, _updateScreen:function(screen) {
    if (this.screen && this.screen !== screen) {
      this.screen.screen.off("set:resolution", this._onScreenResize, this);
      this.screen.screen.off("set:referenceresolution", this._onScreenResize, this);
      this.screen.screen.off("set:scaleblend", this._onScreenResize, this);
      this.screen.screen.off("set:screenspace", this._onScreenSpaceChange, this);
    }
    this.screen = screen;
    if (this.screen) {
      this.screen.screen.on("set:resolution", this._onScreenResize, this);
      this.screen.screen.on("set:referenceresolution", this._onScreenResize, this);
      this.screen.screen.on("set:scaleblend", this._onScreenResize, this);
      this.screen.screen.on("set:screenspace", this._onScreenSpaceChange, this);
      this._setAnchors();
      this._patch();
    } else {
      this._unpatch();
    }
    this.fire("set:screen", this.screen);
    this._anchorDirty = true;
    this.entity.dirtyWorld = true;
    var children = this.entity.getChildren();
    for (var i = 0, l = children.length;i < l;i++) {
      if (children[i].element) {
        children[i].element._updateScreen(screen);
      }
    }
    if (this.screen) {
      this.screen.screen.syncDrawOrder();
    }
  }, _findScreen:function() {
    var screen = this.entity._parent;
    while (screen && !screen.screen) {
      screen = screen._parent;
    }
    return screen;
  }, _onScreenResize:function(res) {
    this._anchorDirty = true;
    this.entity.dirtyWorld = true;
    var minx = this._worldAnchor.x;
    var miny = this._worldAnchor.y;
    var maxx = this._worldAnchor.z;
    var maxy = this._worldAnchor.w;
    var oldWidth = this.width;
    var oldHeight = this.height;
    var px = this.pivot.x;
    var py = this.pivot.y;
    this._setAnchors();
    var p = this.entity.getLocalPosition();
    var l = minx + p.x - this.pivot.x * this.width;
    var r = minx + p.x + (1 - this.pivot.x) * this.width;
    var ldist = l - minx;
    var rdist = r - maxx;
    var newl = this._worldAnchor.x + ldist;
    var newr = this._worldAnchor.z + rdist;
    var newleft = newl - this._worldAnchor.x;
    var newright = newr - this._worldAnchor.x;
    this.width = newright - newleft;
    var b = miny + p.y - (1 - this.pivot.y) * this.height;
    var t = miny + p.y + this.pivot.y * this.height;
    var bdist = b - miny;
    var tdist = t - maxy;
    var newb = this._worldAnchor.y + bdist;
    var newt = this._worldAnchor.w + tdist;
    var newbottom = newb - this._worldAnchor.y;
    var newtop = newt - this._worldAnchor.y;
    this.height = newtop - newbottom;
    p.x = p.x - px * oldWidth + px * this.width;
    p.y = p.y - py * oldHeight + py * this.height;
    this.entity.setLocalPosition(p);
    this.fire("screen:set:resolution", res);
  }, _onScreenSpaceChange:function() {
    this.entity.dirtyWorld = true;
    this.fire("screen:set:screenspace", this.screen.screen.screenSpace);
  }, _setAnchors:function() {
    var resx = 0;
    var resy = 0;
    if (this._parent && this._parent.element) {
      resx = this._parent.element.width;
      resy = this._parent.element.height;
    } else {
      if (this.screen) {
        var res = this.screen.screen.resolution;
        resx = res.x;
        resy = res.y;
      }
    }
    this._worldAnchor.set(this._anchor.x * resx, this._anchor.y * resy, this._anchor.z * resx, this._anchor.w * resy);
  }, onEnable:function() {
    ElementComponent._super.onEnable.call(this);
    if (this._image) {
      this._image.onEnable();
    }
    if (this._text) {
      this._text.onEnable();
    }
    if (this._group) {
      this._group.onEnable();
    }
  }, onDisable:function() {
    ElementComponent._super.onDisable.call(this);
    if (this._image) {
      this._image.onDisable();
    }
    if (this._text) {
      this._text.onDisable();
    }
    if (this._group) {
      this._group.onDisable();
    }
  }, onRemove:function() {
    this._unpatch();
    if (this._image) {
      this._image.destroy();
    }
    if (this._text) {
      this._text.destroy();
    }
  }});
  Object.defineProperty(ElementComponent.prototype, "type", {get:function() {
    return this._type;
  }, set:function(value) {
    if (value !== this._type) {
      if (this._image) {
        this._image.destroy();
        this._image = null;
      }
      if (this._text) {
        this._text.destroy();
        this._text = null;
      }
      if (value === pc.ELEMENTTYPE_IMAGE) {
        this._image = new pc.ImageElement(this);
      } else {
        if (value === pc.ELEMENTTYPE_TEXT) {
          this._text = new pc.TextElement(this);
        }
      }
    }
    this._type = value;
  }});
  Object.defineProperty(ElementComponent.prototype, "drawOrder", {get:function() {
    return this._drawOrder;
  }, set:function(value) {
    this._drawOrder = value;
    this.fire("set:draworder", this._drawOrder);
  }});
  Object.defineProperty(ElementComponent.prototype, "worldLeft", {get:function() {
    return this._worldAnchor.data[0] + this.left;
  }});
  Object.defineProperty(ElementComponent.prototype, "worldRight", {get:function() {
    return this._worldAnchor.data[2] - this.right;
  }});
  Object.defineProperty(ElementComponent.prototype, "worldTop", {get:function() {
    return this._worldAnchor.data[3] - this.top;
  }});
  Object.defineProperty(ElementComponent.prototype, "worldBottom", {get:function() {
    return this._worldAnchor.data[1] + this.bottom;
  }});
  Object.defineProperty(ElementComponent.prototype, "left", {get:function() {
    var p = this.entity.getLocalPosition();
    return p.x + this._width * this._pivot.data[0];
  }, set:function(value) {
    var p = this.entity.getLocalPosition();
    var wr = this.worldRight;
    var wl = this._worldAnchor.data[0] + value;
    this.width = wr - wl;
    p.x = value + this._width * this._pivot.data[0];
    this.entity.setLocalPosition(p);
  }});
  Object.defineProperty(ElementComponent.prototype, "right", {get:function() {
    var p = this.entity.getLocalPosition();
    return this._worldAnchor.data[2] - this._worldAnchor.data[0] - this.left - this._width * (1 - this._pivot.data[0]);
  }, set:function(value) {
    var p = this.entity.getLocalPosition();
    var wl = this.worldLeft;
    var wr = this._worldAnchor.data[2] - value;
    this.width = wr - wl;
    p.x = this._worldAnchor.data[2] - this._worldAnchor.data[0] - value - this._width * (1 - this._pivot.data[0]);
    this.entity.setLocalPosition(p);
  }});
  Object.defineProperty(ElementComponent.prototype, "top", {get:function() {
    var p = this.entity.getLocalPosition();
    return this._worldAnchor.data[3] - this._worldAnchor.data[1] - p.y - this._height * (1 - this._pivot.data[1]);
  }, set:function(value) {
    var p = this.entity.getLocalPosition();
    var wb = this.worldBottom;
    var wt = this._worldAnchor.data[3] - value;
    this.height = wt - wb;
    p.y = this._worldAnchor.data[3] - this._worldAnchor.data[1] - value - this._height * (1 - this._pivot.data[1]);
    this.entity.setLocalPosition(p);
  }});
  Object.defineProperty(ElementComponent.prototype, "bottom", {get:function() {
    var p = this.entity.getLocalPosition();
    return p.y - this._height * this._pivot.data[1];
  }, set:function(value) {
    var p = this.entity.getLocalPosition();
    var wt = this.worldTop;
    var wb = this._worldAnchor.data[1] + value;
    this.height = wt - wb;
    p.y = value + this._height * this._pivot.data[1];
    this.entity.setLocalPosition(p);
  }});
  Object.defineProperty(ElementComponent.prototype, "width", {get:function() {
    return this._width;
  }, set:function(value) {
    this._width = value;
    this.fire("set:width", this._width);
    this.fire("resize", this._width, this._height);
  }});
  Object.defineProperty(ElementComponent.prototype, "height", {get:function() {
    return this._height;
  }, set:function(value) {
    this._height = value;
    this.fire("set:height", this._height);
    this.fire("resize", this._width, this._height);
  }});
  Object.defineProperty(ElementComponent.prototype, "pivot", {get:function() {
    return this._pivot;
  }, set:function(value) {
    if (value instanceof pc.Vec2) {
      this._pivot.set(value.x, value.y);
    } else {
      this._pivot.set(value[0], value[1]);
    }
    this._onScreenResize();
    this.fire("set:pivot", this._pivot);
  }});
  Object.defineProperty(ElementComponent.prototype, "anchor", {get:function() {
    return this._anchor;
  }, set:function(value) {
    if (value instanceof pc.Vec4) {
      this._anchor.set(value.x, value.y, value.z, value.w);
    } else {
      this._anchor.set(value[0], value[1], value[2], value[3]);
    }
    this._setAnchors();
    this._anchorDirty = true;
    this.entity.dirtyWorld = true;
    this.fire("set:anchor", this._anchor);
  }});
  Object.defineProperty(ElementComponent.prototype, "canvasPosition", {get:function() {
    var device = this.system.app.graphicsDevice;
    var ratio = device.width / device.canvas.clientWidth;
    var scale = this.screen.screen.scale * ratio;
    this._canvasPosition.set(this._modelTransform.data[12] / scale, -this._modelTransform.data[13] / scale);
    return this._canvasPosition;
  }});
  var _define = function(name) {
    Object.defineProperty(ElementComponent.prototype, name, {get:function() {
      if (this._text) {
        return this._text[name];
      } else {
        if (this._image) {
          return this._image[name];
        } else {
          return null;
        }
      }
    }, set:function(value) {
      if (this._text) {
        this._text[name] = value;
      } else {
        if (this._image) {
          this._image[name] = value;
        }
      }
    }});
  };
  _define("fontSize");
  _define("color");
  _define("font");
  _define("fontAsset");
  _define("spacing");
  _define("lineHeight");
  _define("text");
  _define("texture");
  _define("textureAsset");
  _define("material");
  _define("materialAsset");
  _define("opacity");
  _define("rect");
  return {ElementComponent:ElementComponent};
}());
pc.extend(pc, function() {
  var ElementComponentData = function() {
    this.enabled = true;
  };
  ElementComponentData = pc.inherits(ElementComponentData, pc.ComponentData);
  return {ElementComponentData:ElementComponentData};
}());
pc.extend(pc, function() {
  var ImageElement = function ImageElement(element) {
    this._element = element;
    this._entity = element.entity;
    this._system = element.system;
    this._textureAsset = null;
    this._texture = null;
    this._materialAsset = null;
    this._material = null;
    this._rect = new pc.Vec4(0, 0, 1, 1);
    this._color = new pc.Color(1, 1, 1, 1);
    this._positions = [];
    this._normals = [];
    this._uvs = [];
    this._indices = [];
    this._mesh = this._createMesh();
    this._node = new pc.GraphNode;
    this._model = new pc.Model;
    this._model.graph = this._node;
    this._meshInstance = new pc.MeshInstance(this._node, this._mesh, this._material);
    this._model.meshInstances.push(this._meshInstance);
    this._drawOrder = 0;
    if (this._entity.enabled) {
      this._system.app.scene.addModel(this._model);
    }
    this._entity.addChild(this._model.graph);
    this._model._entity = this._entity;
    this._onScreenChange(this._element.screen);
    this._element.on("resize", this._onParentResize, this);
    this._element.on("screen:set:screenspace", this._onScreenSpaceChange, this);
    this._element.on("set:screen", this._onScreenChange, this);
    this._element.on("set:draworder", this._onDrawOrderChange, this);
    this._element.on("screen:set:resolution", this._onResolutionChange, this);
  };
  pc.extend(ImageElement.prototype, {destroy:function() {
    if (this._model) {
      this._system.app.scene.removeModel(this._model);
      this._model.destroy();
      this._model = null;
    }
    this._element.off("resize", this._onParentResize, this);
    this._element.off("screen:set:screenspace", this._onScreenSpaceChange, this);
    this._element.off("set:screen", this._onScreenChange, this);
    this._element.off("set:draworder", this._onDrawOrderChange, this);
    this._element.off("screen:set:resolution", this._onResolutionChange, this);
  }, _onResolutionChange:function(res) {
  }, _onParentResize:function() {
    if (this._mesh) {
      this._updateMesh(this._mesh);
    }
  }, _onScreenSpaceChange:function(value) {
    this._updateMaterial(value);
  }, _onScreenChange:function(screen) {
    if (screen) {
      this._updateMaterial(screen.screen.screenSpace);
    } else {
      this._updateMaterial(false);
    }
  }, _onDrawOrderChange:function(order) {
    this._drawOrder = order;
    if (this._meshInstance) {
      this._meshInstance.drawOrder = order;
    }
  }, _updateMaterial:function(screenSpace) {
    if (screenSpace) {
      if (!this._materialAsset) {
        this._material = this._system.defaultScreenSpaceImageMaterial;
      }
      if (this._meshInstance) {
        this._meshInstance.layer = pc.scene.LAYER_HUD;
      }
    } else {
      if (!this._materialAsset) {
        this._material = this._system.defaultImageMaterial;
      }
      if (this._meshInstance) {
        this._meshInstance.layer = pc.scene.LAYER_WORLD;
      }
    }
    if (this._meshInstance) {
      this._meshInstance.material = this._material;
      this._meshInstance.screenSpace = screenSpace;
    }
  }, _createMesh:function() {
    var w = this._element.width;
    var h = this._element.height;
    this._positions[0] = 0;
    this._positions[1] = 0;
    this._positions[2] = 0;
    this._positions[3] = w;
    this._positions[4] = 0;
    this._positions[5] = 0;
    this._positions[6] = w;
    this._positions[7] = h;
    this._positions[8] = 0;
    this._positions[9] = 0;
    this._positions[10] = h;
    this._positions[11] = 0;
    for (var i = 0;i < 12;i += 3) {
      this._normals[i] = 0;
      this._normals[i + 1] = 0;
      this._normals[i + 2] = -1;
    }
    this._uvs[0] = this._rect.data[0];
    this._uvs[1] = this._rect.data[1];
    this._uvs[2] = this._rect.data[0] + this._rect.data[2];
    this._uvs[3] = this._rect.data[1];
    this._uvs[4] = this._rect.data[0] + this._rect.data[2];
    this._uvs[5] = this._rect.data[1] + this._rect.data[3];
    this._uvs[6] = this._rect.data[0];
    this._uvs[7] = this._rect.data[1] + this._rect.data[3];
    this._indices[0] = 0;
    this._indices[1] = 1;
    this._indices[2] = 3;
    this._indices[3] = 2;
    this._indices[4] = 3;
    this._indices[5] = 1;
    var mesh = pc.createMesh(this._system.app.graphicsDevice, this._positions, {uvs:this._uvs, normals:this._normals, indices:this._indices});
    this._updateMesh(mesh);
    return mesh;
  }, _updateMesh:function(mesh) {
    var w = this._element.width;
    var h = this._element.height;
    if (this._element.screen) {
      this._updateMaterial(this._element.screen.screen.screenSpace);
    } else {
      this._updateMaterial();
    }
    this._positions[0] = 0;
    this._positions[1] = 0;
    this._positions[2] = 0;
    this._positions[3] = w;
    this._positions[4] = 0;
    this._positions[5] = 0;
    this._positions[6] = w;
    this._positions[7] = h;
    this._positions[8] = 0;
    this._positions[9] = 0;
    this._positions[10] = h;
    this._positions[11] = 0;
    var hp = this._element.pivot.data[0];
    var vp = this._element.pivot.data[1];
    for (var i = 0;i < this._positions.length;i += 3) {
      this._positions[i] -= hp * w;
      this._positions[i + 1] -= vp * h;
    }
    this._uvs[0] = this._rect.data[0];
    this._uvs[1] = this._rect.data[1];
    this._uvs[2] = this._rect.data[0] + this._rect.data[2];
    this._uvs[3] = this._rect.data[1];
    this._uvs[4] = this._rect.data[0] + this._rect.data[2];
    this._uvs[5] = this._rect.data[1] + this._rect.data[3];
    this._uvs[6] = this._rect.data[0];
    this._uvs[7] = this._rect.data[1] + this._rect.data[3];
    var vb = mesh.vertexBuffer;
    var it = new pc.VertexIterator(vb);
    var numVertices = 4;
    for (var i = 0;i < numVertices;i++) {
      it.element[pc.SEMANTIC_POSITION].set(this._positions[i * 3 + 0], this._positions[i * 3 + 1], this._positions[i * 3 + 2]);
      it.element[pc.SEMANTIC_NORMAL].set(this._normals[i * 3 + 0], this._normals[i * 3 + 1], this._normals[i * 3 + 2]);
      it.element[pc.SEMANTIC_TEXCOORD0].set(this._uvs[i * 2 + 0], this._uvs[i * 2 + 1]);
      it.next();
    }
    it.end();
    mesh.aabb.compute(this._positions);
  }, _onMaterialLoad:function(asset) {
    this.material = asset.resource;
  }, _onMaterialChange:function() {
  }, _onMaterialRemove:function() {
  }, _onTextureLoad:function(asset) {
    this.texture = asset.resource;
  }, _onTextureChange:function(asset) {
  }, _onTextureRemove:function(asset) {
  }, onEnable:function() {
    if (this._model && !this._system.app.scene.containsModel(this._model)) {
      this._system.app.scene.addModel(this._model);
    }
  }, onDisable:function() {
    if (this._model && this._system.app.scene.containsModel(this._model)) {
      this._system.app.scene.removeModel(this._model);
    }
  }});
  Object.defineProperty(ImageElement.prototype, "color", {get:function() {
    return this._color;
  }, set:function(value) {
    this._color.data[0] = value.data[0];
    this._color.data[1] = value.data[1];
    this._color.data[2] = value.data[2];
    if (this._meshInstance) {
      this._meshInstance.setParameter("material_emissive", this._color.data3);
    }
  }});
  Object.defineProperty(ImageElement.prototype, "opacity", {get:function() {
    return this._color.data[3];
  }, set:function(value) {
    this._color.data[3] = value;
    this._meshInstance.setParameter("material_opacity", value);
  }});
  Object.defineProperty(ImageElement.prototype, "rect", {get:function() {
    return this._rect;
  }, set:function(value) {
    if (value instanceof pc.Vec4) {
      this._rect.set(value.x, value.y, value.z, value.w);
    } else {
      this._rect.set(value[0], value[1], value[2], value[3]);
    }
    if (this._mesh) {
      this._updateMesh(this._mesh);
    }
  }});
  Object.defineProperty(ImageElement.prototype, "material", {get:function() {
    return this._material;
  }, set:function(value) {
    if (!value) {
      var screenSpace = this._element.screen ? this._element.screen.screen.screenSpace : false;
      value = screenSpace ? this._system.defaultScreenSpaceImageMaterial : this._system.defaultImageMaterial;
    }
    this._material = value;
    if (value) {
      this._meshInstance.material = value;
      if (value === this._system.defaultScreenSpaceImageMaterial || value === this._system.defaultImageMaterial) {
        if (!this._texture) {
          this._meshInstance.deleteParameter("material_opacity");
          this._meshInstance.deleteParameter("material_emissive");
        }
      }
    }
  }});
  Object.defineProperty(ImageElement.prototype, "materialAsset", {get:function() {
    return this._materialAsset;
  }, set:function(value) {
    var assets = this._system.app.assets;
    var _id = value;
    if (value instanceof pc.Asset) {
      _id = value.id;
    }
    if (this._materialAsset !== _id) {
      if (this._materialAsset) {
        var _prev = assets.get(this._materialAsset);
        _prev.off("load", this._onMaterialLoad, this);
        _prev.off("change", this._onMaterialChange, this);
        _prev.off("remove", this._onMaterialRemove, this);
      }
      this._materialAsset = _id;
      if (this._materialAsset) {
        var asset = assets.get(this._materialAsset);
        asset.on("load", this._onMaterialLoad, this);
        asset.on("change", this._onMaterialChange, this);
        asset.on("remove", this._onMaterialRemove, this);
        if (asset.resource) {
          this._onMaterialLoad(asset);
        } else {
          assets.load(asset);
        }
      } else {
        this.material = null;
      }
    }
  }});
  Object.defineProperty(ImageElement.prototype, "texture", {get:function() {
    return this._texture;
  }, set:function(value) {
    this._texture = value;
    if (value) {
      this._meshInstance.setParameter("texture_emissiveMap", this._texture);
      this._meshInstance.setParameter("texture_opacityMap", this._texture);
      this._meshInstance.setParameter("material_emissive", this._color.data3);
      this._meshInstance.setParameter("material_opacity", this._color.data[3]);
    } else {
      this._meshInstance.deleteParameter("texture_emissiveMap");
      this._meshInstance.deleteParameter("texture_opacityMap");
      if (this._material === this._system.defaultImageMaterial || this._material === this._system.defaultScreenSpaceImageMaterial) {
        this._meshInstance.deleteParameter("material_opacity");
        this._meshInstance.deleteParameter("material_emissive");
      }
    }
  }});
  Object.defineProperty(ImageElement.prototype, "textureAsset", {get:function() {
    return this._textureAsset;
  }, set:function(value) {
    var assets = this._system.app.assets;
    var _id = value;
    if (value instanceof pc.Asset) {
      _id = value.id;
    }
    if (this._textureAsset !== _id) {
      if (this._textureAsset) {
        var _prev = assets.get(this._textureAsset);
        _prev.off("load", this._onTextureLoad, this);
        _prev.off("change", this._onTextureChange, this);
        _prev.off("remove", this._onTextureRemove, this);
      }
      this._textureAsset = _id;
      if (this._textureAsset) {
        var asset = assets.get(this._textureAsset);
        asset.on("load", this._onTextureLoad, this);
        asset.on("change", this._onTextureChange, this);
        asset.on("remove", this._onTextureRemove, this);
        if (asset.resource) {
          this._onTextureLoad(asset);
        } else {
          assets.load(asset);
        }
      } else {
        this.texture = null;
      }
    }
  }});
  return {ImageElement:ImageElement};
}());
pc.extend(pc, function() {
  var TextElement = function TextElement(element) {
    this._element = element;
    this._system = element.system;
    this._entity = element.entity;
    this._text = "";
    this._fontAsset = null;
    this._font = null;
    this._color = new pc.Color(1, 1, 1, 1);
    this._spacing = 1;
    this._fontSize = 32;
    this._lineHeight = 32;
    this.width = 0;
    this.height = 0;
    this._node = new pc.GraphNode;
    this._model = null;
    this._mesh = null;
    this._meshInstance = null;
    this._material = null;
    this._positions = [];
    this._normals = [];
    this._uvs = [];
    this._indices = [];
    this._noResize = false;
    this._onScreenChange(this._element.screen);
    element.on("resize", this._onParentResize, this);
    this._element.on("set:screen", this._onScreenChange, this);
    element.on("screen:set:screenspace", this._onScreenSpaceChange, this);
    element.on("set:draworder", this._onDrawOrderChange, this);
  };
  pc.extend(TextElement.prototype, {destroy:function() {
    if (this._model) {
      this._system.app.scene.removeModel(this._model);
      this._model.destroy();
      this._model = null;
    }
    this._element.off("resize", this._onParentResize, this);
    this._element.off("set:screen", this._onScreenChange, this);
    this._element.off("screen:set:screenspace", this._onScreenSpaceChange, this);
    this._element.off("set:draworder", this._onDrawOrderChange, this);
  }, _onParentResize:function(width, height) {
    if (this._noResize) {
      return;
    }
    if (this._font) {
      this._updateText(this._text);
    }
  }, _onScreenChange:function(screen) {
    if (screen) {
      this._updateMaterial(screen.screen.screenSpace);
    } else {
      this._updateMaterial(false);
    }
  }, _onScreenSpaceChange:function(value) {
    this._updateMaterial(value);
  }, _onDrawOrderChange:function(order) {
    this._drawOrder = order;
    if (this._meshInstance) {
      this._meshInstance.drawOrder = order;
    }
  }, _updateText:function(text) {
    if (text === undefined) {
      text = this._text;
    }
    if (!this._mesh || text.length !== this._text.length) {
      if (this._mesh) {
        this._system.app.scene.removeModel(this._model);
        this._mesh.vertexBuffer.destroy();
        for (var i = 0;i < this._mesh.indexBuffer.length;i++) {
          this._mesh.indexBuffer[i].destroy();
        }
        this._model = null;
        this._mesh = null;
        this._meshInstance = null;
      }
      var screenSpace = this._element.screen && this._element.screen.screen.screenSpace;
      this._updateMaterial(screenSpace);
      this._mesh = this._createMesh(text);
      if (this._node.getParent()) {
        this._node.getParent().removeChild(this._node);
      }
      this._model = new pc.Model;
      this._model.graph = this._node;
      this._meshInstance = new pc.MeshInstance(this._node, this._mesh, this._material);
      this._model.meshInstances.push(this._meshInstance);
      this._meshInstance.drawOrder = this._drawOrder;
      if (screenSpace) {
        this._meshInstance.layer = pc.scene.LAYER_HUD;
      }
      this._meshInstance.screenSpace = screenSpace;
      this._meshInstance.setParameter("texture_msdfMap", this._font.texture);
      this._meshInstance.setParameter("material_emissive", this._color.data3);
      this._meshInstance.setParameter("material_opacity", this._color.data[3]);
      if (this._entity.enabled) {
        this._system.app.scene.addModel(this._model);
      }
      this._entity.addChild(this._model.graph);
      this._model._entity = this._entity;
    } else {
      this._updateMesh(this._mesh, text);
      this._meshInstance.setParameter("texture_msdfMap", this._font.texture);
    }
  }, _updateMaterial:function(screenSpace) {
    if (screenSpace) {
      this._material = this._system.defaultScreenSpaceTextMaterial;
      if (this._meshInstance) {
        this._meshInstance.layer = pc.scene.LAYER_HUD;
      }
    } else {
      this._material = this._system.defaultTextMaterial;
      if (this._meshInstance) {
        this._meshInstance.layer = pc.scene.LAYER_WORLD;
      }
    }
    if (this._meshInstance) {
      this._meshInstance.material = this._material;
      this._meshInstance.screenSpace = screenSpace;
    }
  }, _createMesh:function(text) {
    var l = text.length;
    if (l === 0) {
      l = 1;
      text = " ";
    }
    this._positions = new Array(l * 3 * 4);
    this._normals = new Array(l * 3 * 4);
    this._uvs = new Array(l * 2 * 4);
    this._indices = new Array(l * 3 * 2);
    for (var i = 0;i < l;i++) {
      this._indices.push(i * 4, i * 4 + 1, i * 4 + 3);
      this._indices.push(i * 4 + 2, i * 4 + 3, i * 4 + 1);
    }
    var mesh = pc.createMesh(this._system.app.graphicsDevice, this._positions, {uvs:this._uvs, normals:this._normals, indices:this._indices});
    this._updateMesh(mesh, text);
    return mesh;
  }, _updateMesh:function(mesh, text) {
    var json = this._font.data;
    var vb = mesh.vertexBuffer;
    var it = new pc.VertexIterator(vb);
    var width = 0;
    var height = 0;
    var l = text.length;
    var _x = 0;
    var _y = 0;
    var _z = 0;
    this._positions.length = 0;
    this._normals.length = 0;
    this._uvs.length = 0;
    var miny = Number.MAX_VALUE;
    var maxy = Number.MIN_VALUE;
    var lastWordIndex = 0;
    var lastSoftBreak = 0;
    var lines = 1;
    for (var i = 0;i < l;i++) {
      var char = text.charCodeAt(i);
      if (char === 10 || char === 13) {
        _y -= this._lineHeight;
        _x = 0;
        lastWordIndex = i;
        lastSoftBreak = i;
        lines++;
        continue;
      }
      if (char === 32) {
        lastWordIndex = i + 1;
      }
      var x = 0;
      var y = 0;
      var advance = 0;
      var scale = 1;
      var data = json.chars[char];
      if (data && data.scale) {
        scale = this._fontSize / data.scale;
        advance = this._fontSize * data.xadvance / data.width;
        x = this._fontSize * data.xoffset / data.width;
        y = this._fontSize * data.yoffset / data.height;
      } else {
        advance = .5;
        x = 0;
        y = 0;
        scale = this._fontSize;
      }
      this._positions[i * 4 * 3 + 0] = _x - x;
      this._positions[i * 4 * 3 + 1] = _y - y;
      this._positions[i * 4 * 3 + 2] = _z;
      this._positions[i * 4 * 3 + 3] = _x - (x - scale);
      this._positions[i * 4 * 3 + 4] = _y - y;
      this._positions[i * 4 * 3 + 5] = _z;
      this._positions[i * 4 * 3 + 6] = _x - (x - scale);
      this._positions[i * 4 * 3 + 7] = _y - y + scale;
      this._positions[i * 4 * 3 + 8] = _z;
      this._positions[i * 4 * 3 + 9] = _x - x;
      this._positions[i * 4 * 3 + 10] = _y - y + scale;
      this._positions[i * 4 * 3 + 11] = _z;
      this.width = _x - (x - scale);
      if (this._positions[i * 4 * 3 + 7] > maxy) {
        maxy = this._positions[i * 4 * 3 + 7];
      }
      if (this._positions[i * 4 * 3 + 1] < miny) {
        miny = this._positions[i * 4 * 3 + 1];
      }
      this.height = maxy - miny;
      _x = _x + this._spacing * advance;
      this._normals[i * 4 * 3 + 0] = 0;
      this._normals[i * 4 * 3 + 1] = 0;
      this._normals[i * 4 * 3 + 2] = -1;
      this._normals[i * 4 * 3 + 3] = 0;
      this._normals[i * 4 * 3 + 4] = 0;
      this._normals[i * 4 * 3 + 5] = -1;
      this._normals[i * 4 * 3 + 6] = 0;
      this._normals[i * 4 * 3 + 7] = 0;
      this._normals[i * 4 * 3 + 8] = -1;
      this._normals[i * 4 * 3 + 9] = 0;
      this._normals[i * 4 * 3 + 10] = 0;
      this._normals[i * 4 * 3 + 11] = -1;
      var uv = this._getUv(char);
      this._uvs[i * 4 * 2 + 0] = uv[0];
      this._uvs[i * 4 * 2 + 1] = uv[1];
      this._uvs[i * 4 * 2 + 2] = uv[2];
      this._uvs[i * 4 * 2 + 3] = uv[1];
      this._uvs[i * 4 * 2 + 4] = uv[2];
      this._uvs[i * 4 * 2 + 5] = uv[3];
      this._uvs[i * 4 * 2 + 6] = uv[0];
      this._uvs[i * 4 * 2 + 7] = uv[3];
      this._indices.push(i * 4, i * 4 + 1, i * 4 + 3);
      this._indices.push(i * 4 + 2, i * 4 + 3, i * 4 + 1);
    }
    var hp = this._element.pivot.data[0];
    var vp = this._element.pivot.data[1];
    for (var i = 0;i < this._positions.length;i += 3) {
      this._positions[i] -= hp * this.width;
      this._positions[i + 1] += ((1 - vp) * lines - 1) * this._lineHeight;
    }
    this._noResize = true;
    this._element.width = this.width;
    this._element.height = this.height;
    this._noResize = false;
    var numVertices = l * 4;
    for (var i = 0;i < numVertices;i++) {
      it.element[pc.SEMANTIC_POSITION].set(this._positions[i * 3 + 0], this._positions[i * 3 + 1], this._positions[i * 3 + 2]);
      it.element[pc.SEMANTIC_NORMAL].set(this._normals[i * 3 + 0], this._normals[i * 3 + 1], this._normals[i * 3 + 2]);
      it.element[pc.SEMANTIC_TEXCOORD0].set(this._uvs[i * 2 + 0], this._uvs[i * 2 + 1]);
      it.next();
    }
    it.end();
    mesh.aabb.compute(this._positions);
  }, _onFontLoad:function(asset) {
    if (this.font !== asset.resource) {
      this.font = asset.resource;
    }
  }, _onFontChange:function(asset) {
  }, _onFontRemove:function(asset) {
  }, _getUv:function(char) {
    var data = this._font.data;
    var width = data.info.width;
    var height = data.info.height;
    if (!data.chars[char]) {
      return [0, 0, 1, 1];
    }
    var x = data.chars[char].x;
    var y = data.chars[char].y;
    var x1 = x;
    var y1 = y;
    var x2 = x + data.chars[char].width;
    var y2 = y - data.chars[char].height;
    var edge = 1 - data.chars[char].height / height;
    return [x1 / width, edge - y1 / height, x2 / width, edge - y2 / height];
  }, onEnable:function() {
    if (this._model && !this._system.app.scene.containsModel(this._model)) {
      this._system.app.scene.addModel(this._model);
    }
  }, onDisable:function() {
    if (this._model && this._system.app.scene.containsModel(this._model)) {
      this._system.app.scene.removeModel(this._model);
    }
  }});
  Object.defineProperty(TextElement.prototype, "text", {get:function() {
    return this._text;
  }, set:function(value) {
    var str = value.toString();
    if (this._font) {
      this._updateText(str);
    }
    this._text = str;
  }});
  Object.defineProperty(TextElement.prototype, "color", {get:function() {
    return this._color;
  }, set:function(value) {
    this._color.data[0] = value.data[0];
    this._color.data[1] = value.data[1];
    this._color.data[2] = value.data[2];
    if (this._meshInstance) {
      this._meshInstance.setParameter("material_emissive", this._color.data3);
    }
  }});
  Object.defineProperty(TextElement.prototype, "opacity", {get:function() {
    return this._color.data[3];
  }, set:function(value) {
    this._color.data[3] = value;
    this._meshInstance.setParameter("material_opacity", value);
  }});
  Object.defineProperty(TextElement.prototype, "lineHeight", {get:function() {
    return this._lineHeight;
  }, set:function(value) {
    var _prev = this._lineHeight;
    this._lineHeight = value;
    if (_prev !== value && this._font) {
      this._updateText();
    }
  }});
  Object.defineProperty(TextElement.prototype, "spacing", {get:function() {
    return this._spacing;
  }, set:function(value) {
    var _prev = this._spacing;
    this._spacing = value;
    if (_prev !== value && this._font) {
      this._updateText();
    }
  }});
  Object.defineProperty(TextElement.prototype, "fontSize", {get:function() {
    return this._fontSize;
  }, set:function(value) {
    var _prev = this._fontSize;
    this._fontSize = value;
    if (_prev !== value && this._font) {
      this._updateText();
    }
  }});
  Object.defineProperty(TextElement.prototype, "fontAsset", {get function() {
    return this._fontAsset;
  }, set:function(value) {
    var assets = this._system.app.assets;
    var _id = value;
    if (value instanceof pc.Asset) {
      _id = value.id;
    }
    if (this._fontAsset !== _id) {
      if (this._fontAsset) {
        var _prev = assets.get(this._fontAsset);
        _prev.off("load", this._onFontLoad, this);
        _prev.off("change", this._onFontChange, this);
        _prev.off("remove", this._onFontRemove, this);
      }
      this._fontAsset = _id;
      if (this._fontAsset) {
        var asset = assets.get(this._fontAsset);
        asset.on("load", this._onFontLoad, this);
        asset.on("change", this._onFontChange, this);
        asset.on("remove", this._onFontRemove, this);
        if (asset.resource) {
          this._onFontLoad(asset);
        } else {
          assets.load(asset);
        }
      }
    }
  }});
  Object.defineProperty(TextElement.prototype, "font", {get:function() {
    return this._font;
  }, set:function(value) {
    this._font = value;
    if (this._font) {
      this._updateText();
    }
  }});
  return {TextElement:TextElement};
}());
pc.extend(pc, function() {
  pc.FONT_MSDF = "msdf";
  var Font = function(texture, data) {
    this.type = pc.FONT_MSDF;
    this.em = 1;
    this.texture = texture;
    this._data = null;
    this.data = data;
  };
  Font.prototype = {};
  Object.defineProperty(Font.prototype, "data", {get:function() {
    return this._data;
  }, set:function(value) {
    this._data = value;
  }});
  return {FONT_MSDF:pc.FONT_MSDF, Font:Font};
}());
pc.extend(pc, function() {
  var ZoneComponentSystem = function ZoneComponentSystem(app) {
    this.id = "zone";
    this.app = app;
    app.systems.add(this.id, this);
    this.ComponentType = pc.ZoneComponent;
    this.DataType = pc.ZoneComponentData;
    this.schema = ["enabled"];
    this.on("beforeremove", this._onBeforeRemove, this);
  };
  ZoneComponentSystem = pc.inherits(ZoneComponentSystem, pc.ComponentSystem);
  pc.extend(ZoneComponentSystem.prototype, {initializeComponentData:function(component, data, properties) {
    component.enabled = data.hasOwnProperty("enabled") ? !!data.enabled : true;
    if (data.size) {
      if (data.size instanceof pc.Vec3) {
        component.size.copy(data.size);
      } else {
        if (data.size instanceof Array && data.size.length >= 3) {
          component.size.set(data.size[0], data.size[1], data.size[2]);
        }
      }
    }
  }, cloneComponent:function(entity, clone) {
    var data = {size:entity.zone.size};
    return this.addComponent(clone, data);
  }, _onBeforeRemove:function(entity, component) {
    component._onBeforeRemove();
  }});
  return {ZoneComponentSystem:ZoneComponentSystem};
}());
pc.extend(pc, function() {
  var ZoneComponent = function ZoneComponent(system, entity) {
    this._oldState = true;
    this._size = new pc.Vec3;
    this.on("set_enabled", this._onSetEnabled, this);
  };
  ZoneComponent = pc.inherits(ZoneComponent, pc.Component);
  pc.extend(ZoneComponent.prototype, {onEnable:function() {
    ZoneComponent._super.onEnable.call(this);
    this._checkState();
  }, onDisable:function() {
    ZoneComponent._super.onDisable.call(this);
    this._checkState();
  }, _onSetEnabled:function(prop, old, value) {
    this._checkState();
  }, _checkState:function() {
    var state = this.enabled && this.entity.enabled;
    if (state === this._oldState) {
      return;
    }
    this._oldState = state;
    this.fire("enable");
    this.fire("state", this.enabled);
  }, _onBeforeRemove:function() {
    this.fire("remove");
  }});
  Object.defineProperty(ZoneComponent.prototype, "size", {set:function(data) {
    if (data instanceof pc.Vec3) {
      this._size.copy(data);
    } else {
      if (data instanceof Array && data.length >= 3) {
        this.size.set(data[0], data[1], data[2]);
      }
    }
  }, get:function() {
    return this._size;
  }});
  return {ZoneComponent:ZoneComponent};
}());
pc.extend(pc, function() {
  var ZoneComponentData = function() {
    this.enabled = true;
  };
  ZoneComponentData = pc.inherits(ZoneComponentData, pc.ComponentData);
  return {ZoneComponentData:ZoneComponentData};
}());
pc.extend(pc, function() {
  var Entity = function(app) {
    this._guid = pc.guid.create();
    this._batchHandle = null;
    this.c = {};
    this._app = app;
    if (!app) {
      this._app = pc.Application.getApplication();
      if (!this._app) {
        console.error("Couldn't find current application");
      }
    }
    pc.events.attach(this);
  };
  Entity = pc.inherits(Entity, pc.GraphNode);
  Entity.prototype.addComponent = function(type, data) {
    var system = this._app.systems[type];
    if (system) {
      if (!this.c[type]) {
        return system.addComponent(this, data);
      } else {
        logERROR(pc.string.format("Entity already has {0} Component", type));
      }
    } else {
      logERROR(pc.string.format("System: '{0}' doesn't exist", type));
      return null;
    }
  };
  Entity.prototype.removeComponent = function(type) {
    var system = this._app.systems[type];
    if (system) {
      if (this.c[type]) {
        system.removeComponent(this);
      } else {
        logERROR(pc.string.format("Entity doesn't have {0} Component", type));
      }
    } else {
      logERROR(pc.string.format("System: '{0}' doesn't exist", type));
    }
  };
  Entity.prototype.getGuid = function() {
    return this._guid;
  };
  Entity.prototype.setGuid = function(guid) {
    this._guid = guid;
  };
  Entity.prototype._notifyHierarchyStateChanged = function(node, enabled) {
    var enableFirst = false;
    if (node === this && this._app._enableList.length === 0) {
      enableFirst = true;
    }
    node._onHierarchyStateChanged(enabled);
    if (node._onHierarchyStatePostChanged) {
      this._app._enableList.push(node);
    }
    var i, len;
    var c = node._children;
    for (i = 0, len = c.length;i < len;i++) {
      if (c[i]._enabled) {
        this._notifyHierarchyStateChanged(c[i], enabled);
      }
    }
    if (enableFirst) {
      for (i = 0, len = this._app._enableList.length;i < len;i++) {
        this._app._enableList[i]._onHierarchyStatePostChanged();
      }
      this._app._enableList.length = 0;
    }
  };
  Entity.prototype._onHierarchyStateChanged = function(enabled) {
    pc.Entity._super._onHierarchyStateChanged.call(this, enabled);
    var component;
    var components = this.c;
    for (var type in components) {
      if (components.hasOwnProperty(type)) {
        component = components[type];
        if (component.enabled) {
          if (enabled) {
            component.onEnable();
          } else {
            component.onDisable();
          }
        }
      }
    }
  };
  Entity.prototype._onHierarchyStatePostChanged = function() {
    var components = this.c;
    for (var type in components) {
      if (components.hasOwnProperty(type)) {
        components[type].onPostStateChange();
      }
    }
  };
  Entity.prototype.setRequest = function(request) {
    this._request = request;
  };
  Entity.prototype.getRequest = function() {
    return this._request;
  };
  Entity.prototype.findByGuid = function(guid) {
    if (this._guid === guid) {
      return this;
    }
    for (var i = 0;i < this._children.length;i++) {
      if (this._children[i].findByGuid) {
        var found = this._children[i].findByGuid(guid);
        if (found !== null) {
          return found;
        }
      }
    }
    return null;
  };
  Entity.prototype.destroy = function() {
    var childGuids;
    var name;
    for (name in this.c) {
      this.c[name].enabled = false;
    }
    for (name in this.c) {
      this.c[name].system.removeComponent(this);
    }
    if (this._parent) {
      this._parent.removeChild(this);
    }
    var children = this._children;
    var length = children.length;
    var child = children.shift();
    while (child) {
      if (child instanceof pc.Entity) {
        child.destroy();
      }
      child = children.shift();
    }
  };
  Entity.prototype.clone = function() {
    var type;
    var c = new pc.Entity(this._app);
    pc.Entity._super._cloneInternal.call(this, c);
    for (type in this.c) {
      var component = this.c[type];
      component.system.cloneComponent(this, c);
    }
    var i;
    for (i = 0;i < this._children.length;i++) {
      var child = this._children[i];
      if (child instanceof pc.Entity) {
        c.addChild(child.clone());
      }
    }
    return c;
  };
  return {Entity:Entity};
}());
pc.extend(pc, function() {
  var ResourceLoader = function() {
    this._handlers = {};
    this._requests = {};
    this._cache = {};
  };
  ResourceLoader.prototype = {addHandler:function(type, handler) {
    this._handlers[type] = handler;
    handler._loader = this;
  }, removeHandler:function(type) {
    delete this._handlers[type];
  }, getHandler:function(type) {
    return this._handlers[type];
  }, load:function(url, type, callback) {
    var handler = this._handlers[type];
    if (!handler) {
      var err = "No handler for asset type: " + type;
      callback(err);
      return;
    }
    var key = url + type;
    if (this._cache[key] !== undefined) {
      callback(null, this._cache[key]);
    } else {
      if (this._requests[key]) {
        this._requests[key].push(callback);
      } else {
        this._requests[key] = [callback];
        handler.load(url, function(err, data, extra) {
          if (!this._requests[key]) {
            return;
          }
          var i, len = this._requests[key].length;
          if (!err) {
            var resource = handler.open(url, data);
            this._cache[key] = resource;
            for (i = 0;i < len;i++) {
              this._requests[key][i](null, resource, extra);
            }
          } else {
            for (i = 0;i < len;i++) {
              this._requests[key][i](err);
            }
          }
          delete this._requests[key];
        }.bind(this));
      }
    }
  }, open:function(type, data) {
    var handler = this._handlers[type];
    if (!handler) {
      console.warn("No resource handler found for: " + type);
      return data;
    }
    return handler.open(null, data);
  }, patch:function(asset, assets) {
    var handler = this._handlers[asset.type];
    if (!handler) {
      console.warn("No resource handler found for: " + asset.type);
      return;
    }
    if (handler.patch) {
      handler.patch(asset, assets);
    }
  }, clearCache:function(url, type) {
    delete this._cache[url + type];
  }, getFromCache:function(url, type) {
    if (this._cache[url + type]) {
      return this._cache[url + type];
    }
  }, destroy:function() {
    this._handlers = {};
    this._requests = {};
    this._cache = {};
  }};
  return {ResourceLoader:ResourceLoader};
}());
pc.extend(pc, function() {
  var AnimationHandler = function() {
  };
  AnimationHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (err) {
        callback(pc.string.format("Error loading animation resource: {0} [{1}]", url, err));
      } else {
        callback(null, response);
      }
    }.bind(this));
  }, open:function(url, data) {
    return this["_parseAnimationV" + data.animation.version](data);
  }, _parseAnimationV3:function(data) {
    var animData = data.animation;
    var anim = new pc.Animation;
    anim.setName(animData.name);
    anim.duration = animData.duration;
    for (var i = 0;i < animData.nodes.length;i++) {
      var node = new pc.Node;
      var n = animData.nodes[i];
      node._name = n.name;
      for (var j = 0;j < n.keys.length;j++) {
        var k = n.keys[j];
        var t = k.time;
        var p = k.pos;
        var r = k.rot;
        var s = k.scale;
        var pos = new pc.Vec3(p[0], p[1], p[2]);
        var rot = (new pc.Quat).setFromEulerAngles(r[0], r[1], r[2]);
        var scl = new pc.Vec3(s[0], s[1], s[2]);
        var key = new pc.Key(t, pos, rot, scl);
        node._keys.push(key);
      }
      anim.addNode(node);
    }
    return anim;
  }, _parseAnimationV4:function(data) {
    var animData = data.animation;
    var anim = new pc.Animation;
    anim.setName(animData.name);
    anim.duration = animData.duration;
    for (var i = 0;i < animData.nodes.length;i++) {
      var node = new pc.Node;
      var n = animData.nodes[i];
      node._name = n.name;
      var defPos = n.defaults.p;
      var defRot = n.defaults.r;
      var defScl = n.defaults.s;
      for (var j = 0;j < n.keys.length;j++) {
        var k = n.keys[j];
        var t = k.t;
        var p = defPos ? defPos : k.p;
        var r = defRot ? defRot : k.r;
        var s = defScl ? defScl : k.s;
        var pos = new pc.Vec3(p[0], p[1], p[2]);
        var rot = (new pc.Quat).setFromEulerAngles(r[0], r[1], r[2]);
        var scl = new pc.Vec3(s[0], s[1], s[2]);
        var key = new pc.Key(t, pos, rot, scl);
        node._keys.push(key);
      }
      anim.addNode(node);
    }
    return anim;
  }};
  return {AnimationHandler:AnimationHandler};
}());
pc.extend(pc, function() {
  var ie = function() {
    var ua = window.navigator.userAgent;
    var msie = ua.indexOf("MSIE ");
    if (msie > 0) {
      return parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)), 10);
    }
    var trident = ua.indexOf("Trident/");
    if (trident > 0) {
      var rv = ua.indexOf("rv:");
      return parseInt(ua.substring(rv + 3, ua.indexOf(".", rv)), 10);
    }
    return false;
  }();
  var AudioHandler = function(manager) {
    this.manager = manager;
  };
  AudioHandler.prototype = {_isSupported:function(url) {
    var toMIME = {".ogg":"audio/ogg", ".mp3":"audio/mpeg", ".wav":"audio/x-wav", ".mp4a":"audio/mp4", ".m4a":"audio/mp4", ".mp4":"audio/mp4", ".aac":"audio/aac"};
    var ext = pc.path.getExtension(url);
    if (toMIME[ext]) {
      return true;
    } else {
      return false;
    }
  }, load:function(url, callback) {
    var success = function(resource) {
      callback(null, new pc.Sound(resource));
    };
    var error = function(msg) {
      msg = msg || "Error loading audio url: " + url;
      console.warn(msg);
      callback(msg);
    };
    if (this._createSound) {
      if (!this._isSupported(url)) {
        error(pc.string.format("Audio format for {0} not supported", url));
        return;
      }
      this._createSound(url, success, error);
    } else {
      error(null);
    }
  }, open:function(url, data) {
    return data;
  }};
  if (pc.SoundManager.hasAudioContext()) {
    AudioHandler.prototype._createSound = function(url, success, error) {
      var manager = this.manager;
      if (!manager.context) {
        error("Audio manager has no audio context");
        return;
      }
      pc.http.get(url, function(err, response) {
        if (err) {
          error(err);
          return;
        }
        manager.context.decodeAudioData(response, success, error);
      });
    };
  } else {
    if (pc.SoundManager.hasAudio()) {
      AudioHandler.prototype._createSound = function(url, success, error) {
        var audio = null;
        try {
          audio = new Audio;
        } catch (e) {
          error("No support for Audio element");
          return;
        }
        if (ie) {
          document.body.appendChild(audio);
        }
        var onReady = function() {
          audio.removeEventListener("canplaythrough", onReady);
          if (ie) {
            document.body.removeChild(audio);
          }
          success(audio);
        };
        audio.onerror = function() {
          audio.onerror = null;
          if (ie) {
            document.body.removeChild(audio);
          }
          error();
        };
        audio.addEventListener("canplaythrough", onReady);
        audio.src = url;
      };
    }
  }
  return {AudioHandler:AudioHandler};
}());
pc.extend(pc, function() {
  var CubemapHandler = function(device, assets, loader) {
    this._device = device;
    this._assets = assets;
    this._loader = loader;
  };
  CubemapHandler.prototype = {load:function(url, callback) {
  }, open:function(url, data) {
  }, patch:function(assetCubeMap, assets) {
    var self = this;
    var loaded = false;
    if (!assetCubeMap.resources[0]) {
      assetCubeMap.resources[0] = new pc.Texture(this._device, {format:pc.PIXELFORMAT_R8_G8_B8_A8, cubemap:true, mipmaps:true, fixCubemapSeams:!!assetCubeMap._dds});
      loaded = true;
    }
    if (!assetCubeMap.file) {
      delete assetCubeMap._dds;
    } else {
      if (assetCubeMap.file && !assetCubeMap._dds) {
        var url = assetCubeMap.getFileUrl();
        assets._loader.load(url + "?t=" + assetCubeMap.file.hash, "texture", function(err, texture) {
          if (!err) {
            assets._loader.patch({resource:texture, type:"texture", data:assetCubeMap.data}, assets);
            assetCubeMap._dds = texture;
            self.patch(assetCubeMap, assets);
          } else {
            assets.fire("error", err, assetCubeMap);
            assets.fire("error:" + assetCubeMap.id, err, assetCubeMap);
            assetCubeMap.fire("error", err, assetCubeMap);
            return;
          }
        });
      }
    }
    if ((!assetCubeMap.file || !assetCubeMap._dds) && assetCubeMap.resources[1]) {
      assetCubeMap.resources = [assetCubeMap.resources[0]];
      loaded = true;
    } else {
      if (assetCubeMap._dds && !assetCubeMap.resources[1]) {
        assetCubeMap.resources = [assetCubeMap.resources[0]];
        assetCubeMap._dds.fixCubemapSeams = true;
        assetCubeMap._dds.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
        assetCubeMap._dds.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
        assetCubeMap.resources.push(assetCubeMap._dds);
        for (var i = 1;i < 6;i++) {
          var mip = new pc.Texture(this._device, {cubemap:true, fixCubemapSeams:true, mipmaps:false, format:assetCubeMap._dds.format, rgbm:assetCubeMap._dds.rgbm, width:Math.pow(2, 7 - i), height:Math.pow(2, 7 - i)});
          mip._levels[0] = assetCubeMap._dds._levels[i];
          mip.upload();
          assetCubeMap.resources.push(mip);
        }
        loaded = true;
      }
    }
    var cubemap = assetCubeMap.resource;
    if (cubemap.name !== assetCubeMap.name) {
      cubemap.name = assetCubeMap.name;
    }
    if (assetCubeMap.data.hasOwnProperty("rgbm") && cubemap.rgbm !== !!assetCubeMap.data.rgbm) {
      cubemap.rgbm = !!assetCubeMap.data.rgbm;
    }
    cubemap.fixCubemapSeams = !!assetCubeMap._dds;
    if (assetCubeMap.data.hasOwnProperty("minFilter") && cubemap.minFilter !== assetCubeMap.data.minFilter) {
      cubemap.minFilter = assetCubeMap.data.minFilter;
    }
    if (assetCubeMap.data.hasOwnProperty("magFilter") && cubemap.magFilter !== assetCubeMap.data.magFilter) {
      cubemap.magFilter = assetCubeMap.data.magFilter;
    }
    if (assetCubeMap.data.hasOwnProperty("anisotropy") && cubemap.anisotropy !== assetCubeMap.data.anisotropy) {
      cubemap.anisotropy = assetCubeMap.data.anisotropy;
    }
    if (cubemap.addressU !== pc.ADDRESS_CLAMP_TO_EDGE) {
      cubemap.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
    }
    if (cubemap.addressV !== pc.ADDRESS_CLAMP_TO_EDGE) {
      cubemap.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
    }
    this._patchTextureFaces(assetCubeMap, assets);
    if (loaded) {
      assets.fire("load", assetCubeMap);
      assets.fire("load:" + assetCubeMap.id, assetCubeMap);
      assetCubeMap.fire("load", assetCubeMap);
    }
  }, _patchTexture:function() {
    this.registry._loader._handlers.cubemap._patchTextureFaces(this, this.registry);
  }, _patchTextureFaces:function(assetCubeMap, assets) {
    if (!assetCubeMap.loadFaces && assetCubeMap.file) {
      return;
    }
    var cubemap = assetCubeMap.resource;
    var sources = [];
    var count = 0;
    var levelsUpdated = false;
    var self = this;
    if (!assetCubeMap._levelsEvents) {
      assetCubeMap._levelsEvents = [null, null, null, null, null, null];
    }
    assetCubeMap.data.textures.forEach(function(id, index) {
      var assetAdded = function(asset) {
        asset.ready(assetReady);
        assets.load(asset);
      };
      var assetReady = function(asset) {
        count++;
        sources[index] = asset && asset.resource.getSource() || null;
        var evtAsset = assetCubeMap._levelsEvents[index];
        if (evtAsset !== asset) {
          if (evtAsset) {
            evtAsset.off("load", self._patchTexture, assetCubeMap);
          }
          if (asset) {
            asset.on("load", self._patchTexture, assetCubeMap);
          }
          assetCubeMap._levelsEvents[index] = asset || null;
        }
        if (sources[index] !== cubemap._levels[0][index]) {
          levelsUpdated = true;
        }
        if (count === 6 && levelsUpdated) {
          cubemap.setSource(sources);
          assets.fire("load", assetCubeMap);
          assets.fire("load:" + assetCubeMap.id, assetCubeMap);
          assetCubeMap.fire("load", assetCubeMap);
        }
      };
      var asset = assets.get(assetCubeMap.data.textures[index]);
      if (asset) {
        asset.ready(assetReady);
        assets.load(asset);
      } else {
        if (id) {
          assets.once("load:" + id, assetReady);
          assets.once("add:" + id, assetAdded);
        } else {
          assetReady(null);
        }
      }
    });
  }};
  return {CubemapHandler:CubemapHandler};
}());
pc.extend(pc, function() {
  var JsonHandler = function() {
  };
  JsonHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback(pc.string.format("Error loading JSON resource: {0} [{1}]", url, err));
      }
    });
  }, open:function(url, data) {
    return data;
  }, patch:function(asset, assets) {
  }};
  return {JsonHandler:JsonHandler};
}());
pc.extend(pc, function() {
  var PARAMETER_TYPES = {ambient:"vec3", ambientTnumber:"boolean", aoMap:"texture", aoMapVertexColor:"boolean", aoMapChannel:"string", aoMapUv:"number", aoMapTiling:"vec2", aoMapOffset:"vec2", occludeSpecular:"boolean", diffuse:"vec3", diffuseMap:"texture", diffuseMapVertexColor:"boolean", diffuseMapChannel:"string", diffuseMapUv:"number", diffuseMapTiling:"vec2", diffuseMapOffset:"vec2", diffuseMapTnumber:"boolean", specular:"vec3", specularMapVertexColor:"boolean", specularMapChannel:"string", 
  specularMapUv:"number", specularMap:"texture", specularMapTiling:"vec2", specularMapOffset:"vec2", specularMapTnumber:"boolean", specularAntialias:"boolean", useMetalness:"boolean", metalnessMap:"texture", metalnessMapVertexColor:"boolean", metalnessMapChannel:"string", metalnessMapUv:"number", metalnessMapTiling:"vec2", metalnessMapOffset:"vec2", metalnessMapTnumber:"boolean", metalness:"number", conserveEnergy:"boolean", shininess:"number", glossMap:"texture", glossMapVertexColor:"boolean", glossMapChannel:"string", 
  glossMapUv:"number", glossMapTiling:"vec2", glossMapOffset:"vec2", fresnelModel:"number", fresnelFactor:"float", emissive:"vec3", emissiveMap:"texture", emissiveMapVertexColor:"boolean", emissiveMapChannel:"string", emissiveMapUv:"number", emissiveMapTiling:"vec2", emissiveMapOffset:"vec2", emissiveMapTint:"boolean", emissiveIntensity:"number", normalMap:"texture", normalMapTiling:"vec2", normalMapOffset:"vec2", normalMapUv:"number", bumpMapFactor:"number", heightMap:"texture", heightMapChannel:"string", 
  heightMapUv:"number", heightMapTiling:"vec2", heightMapOffset:"vec2", heightMapFactor:"number", alphaTest:"number", opacity:"number", opacityMap:"texture", opacityMapVertexColor:"boolean", opacityMapChannel:"string", opacityMapUv:"number", opacityMapTiling:"vec2", opacityMapOffset:"vec2", reflectivity:"number", refraction:"number", refractionIndex:"number", sphereMap:"texture", cubeMap:"cubemap", cubeMapProjection:"number", cubeMapProjectionBox:"boundingbox", lightMap:"texture", lightMapVertexColor:"boolean", 
  lightMapChannel:"string", lightMapUv:"number", lightMapTiling:"vec2", lightMapOffset:"vec2", depthTest:"boolean", depthWrite:"boolean", cull:"number", blendType:"number", shadowSampleType:"number", shadingModel:"number"};
  var placeholders = {};
  var placeholdersMapping = {aoMap:"white", diffuseMap:"gray", specularMap:"gray", metalnessMap:"black", glossMap:"gray", emissiveMap:"gray", normalMap:"normal", heightMap:"gray", opacityMap:"gray", sphereMap:"gray", lightMap:"white"};
  var onCubemapAssetLoad = function(asset, attribute, newValue, oldValue) {
    var props = ["cubeMap", "prefilteredCubeMap128", "prefilteredCubeMap64", "prefilteredCubeMap32", "prefilteredCubeMap16", "prefilteredCubeMap8", "prefilteredCubeMap4"];
    for (var i = 0;i < props.length;i++) {
      if (this[props[i]] !== asset.resources[i]) {
        this[props[i]] = asset.resources[i];
      }
    }
    this.update();
  };
  var MaterialHandler = function(app) {
    this._assets = app.assets;
    this._device = app.graphicsDevice;
    this._createPlaceholders();
  };
  MaterialHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        if (callback) {
          callback(null, response);
        }
      } else {
        if (callback) {
          callback(pc.string.format("Error loading material: {0} [{1}]", url, err));
        }
      }
    });
  }, open:function(url, data) {
    var material = new pc.StandardMaterial;
    if (!data.parameters) {
      this._createParameters(data);
    }
    material.init(data);
    material._data = data;
    return material;
  }, _createPlaceholders:function() {
    var textures = {white:[255, 255, 255, 255], gray:[128, 128, 128, 255], black:[0, 0, 0, 255], normal:[128, 128, 255, 255]};
    for (var key in textures) {
      if (!textures.hasOwnProperty(key)) {
        continue;
      }
      var texture = placeholders[key] = new pc.Texture(this._device, {width:2, height:2, format:pc.PIXELFORMAT_R8_G8_B8_A8});
      var pixels = texture.lock();
      for (var i = 0;i < 4;i++) {
        for (var c = 0;c < 4;c++) {
          pixels[i * 4 + c] = textures[key][c];
        }
      }
      texture.unlock();
    }
  }, _createParameters:function(data) {
    var parameters = [];
    if (!data.shadingModel) {
      data.shadingModel = data.shader === "blinn" ? pc.SPECULAR_BLINN : pc.SPECULAR_PHONG;
    }
    var shader = data.shader;
    delete data.shader;
    for (var key in data) {
      if (!data.hasOwnProperty(key)) {
        continue;
      }
      parameters.push({name:key, type:PARAMETER_TYPES[key], data:data[key]});
    }
    data.shader = shader;
    data.parameters = parameters;
  }, patch:function(asset, assets) {
    if (asset.data.shader === undefined) {
      asset.data = asset.resource._data;
      delete asset.resource._data;
    }
    this._updateStandardMaterial(asset, asset.data, assets);
    asset.off("change", this._onAssetChange, this);
    asset.on("change", this._onAssetChange, this);
    asset.on("unload", this._onAssetUnload, this);
  }, _onAssetChange:function(asset, attribute, value) {
    if (attribute === "data") {
      this._updateStandardMaterial(asset, value, this._assets);
    }
  }, _onAssetUnload:function(asset) {
    delete asset.data.parameters;
    delete asset.data.chunks;
    delete asset.data.name;
  }, _updateStandardMaterial:function(asset, data, assets) {
    var material = asset.resource;
    var dir;
    if (asset.file) {
      dir = pc.path.getDirectory(asset.getFileUrl());
    }
    data.name = asset.name;
    if (!data.parameters) {
      this._createParameters(data);
    }
    var pathMapping = data.mapping_format === "path";
    data.chunks = asset.resource.chunks;
    data.parameters.forEach(function(param, i) {
      var id;
      if (param.type === "texture") {
        if (!material._assetHandlers) {
          material._assetHandlers = {};
        }
        var handler = material._assetHandlers[param.name];
        if (param.data && !(param.data instanceof pc.Texture)) {
          if (pathMapping) {
            asset = assets.getByUrl(pc.path.join(dir, param.data));
          } else {
            id = param.data;
            asset = assets.get(param.data);
          }
          if (handler) {
            assets.off("load:" + handler.id, handler.bind);
            assets.off("add:" + handler.id, handler.add);
            assets.off("remove:" + handler.id, handler.remove);
            if (handler.url) {
              assets.off("add:url:" + handler.url, handler.add);
              assets.off("remove:url:" + handler.url, handler.remove);
            }
            material._assetHandlers[param.name] = null;
          }
          handler = material._assetHandlers[param.name] = {id:id, url:pathMapping ? pc.path.join(dir, param.data) : "", bind:function(asset) {
            data.parameters[i].data = asset.resource;
            material[data.parameters[i].name] = asset.resource;
            material.update();
          }, add:function(asset) {
            assets.load(asset);
          }, remove:function(asset) {
            if (material[data.parameters[i].name] === asset.resource) {
              data.parameters[i].data = null;
              material[data.parameters[i].name] = null;
              material.update();
            }
          }};
          if (id) {
            assets.on("load:" + id, handler.bind);
            assets.on("remove:" + id, handler.remove);
          } else {
            if (pathMapping) {
              assets.on("load:url:" + pc.path.join(dir, param.data), handler.bind);
              assets.on("remove:url:" + pc.path.join(dir, param.data), handler.remove);
            }
          }
          if (asset) {
            if (asset.resource) {
              handler.bind(asset);
            } else {
              if (placeholdersMapping[data.parameters[i].name]) {
                var texture = placeholders[placeholdersMapping[data.parameters[i].name]];
                if (texture) {
                  data.parameters[i].data = texture;
                  material[data.parameters[i].name] = texture;
                }
              }
            }
            assets.load(asset);
          } else {
            if (id) {
              assets.once("add:" + id, handler.add);
            } else {
              if (pathMapping) {
                assets.once("add:url:" + handler.url, handler.add);
              }
            }
          }
        } else {
          if (handler && !param.data) {
            assets.off("load:" + handler.id, handler.bind);
            assets.off("add:" + handler.id, handler.add);
            assets.off("remove:" + handler.id, handler.remove);
            if (handler.url) {
              assets.off("add:url:" + handler.url, handler.add);
              assets.off("remove:url:" + handler.url, handler.remove);
            }
            material._assetHandlers[param.name] = null;
          }
        }
      } else {
        if (param.type === "cubemap" && param.data && !(param.data instanceof pc.Texture)) {
          if (pathMapping) {
            asset = assets.getByUrl(pc.path.join(dir, param.data));
          } else {
            id = param.data;
            asset = assets.get(param.data);
          }
          var onAdd = function(asset) {
            if (data.shadingModel === pc.SPECULAR_PHONG) {
              asset.loadFaces = true;
            }
            asset.ready(onReady);
            assets.load(asset);
          };
          var onReady = function(asset) {
            param.data = asset.resource;
            if (asset.resources.length > 1) {
              data.parameters.push({name:"prefilteredCubeMap128", data:asset.resources[1]});
              data.parameters.push({name:"prefilteredCubeMap64", data:asset.resources[2]});
              data.parameters.push({name:"prefilteredCubeMap32", data:asset.resources[3]});
              data.parameters.push({name:"prefilteredCubeMap16", data:asset.resources[4]});
              data.parameters.push({name:"prefilteredCubeMap8", data:asset.resources[5]});
              data.parameters.push({name:"prefilteredCubeMap4", data:asset.resources[6]});
            }
            material.init(data);
            asset.off("load", onCubemapAssetLoad, material);
            asset.on("load", onCubemapAssetLoad, material);
          };
          if (asset) {
            onAdd(asset);
          } else {
            if (id) {
              assets.once("add:" + id, onAdd);
            } else {
              if (pathMapping) {
                assets.once("add:url:" + pc.path.join(dir, param.data), function(asset) {
                  asset.ready(function(asset) {
                    data.parameters[i].data = asset.resource;
                    material.init(data);
                    asset.off("load", onCubemapAssetLoad, material);
                    asset.on("load", onCubemapAssetLoad, material);
                  });
                  assets.load(asset);
                });
              }
            }
          }
        }
      }
    });
    material.init(data);
  }};
  return {MaterialHandler:MaterialHandler, getMaterialParamType:function(name) {
    return PARAMETER_TYPES[name];
  }};
}());
pc.extend(pc, function() {
  var ModelHandler = function(device) {
    this._device = device;
  };
  ModelHandler.DEFAULT_MATERIAL = pc.Scene.defaultMaterial;
  ModelHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!callback) {
        return;
      }
      if (!err) {
        callback(null, response);
      } else {
        callback(pc.string.format("Error loading model: {0} [{1}]", url, err));
      }
    });
  }, open:function(url, data) {
    if (!data.model) {
      return;
    }
    if (data.model.version <= 1) {
      logERROR(pc.string.format("Asset: {0}, is an old model format. Upload source assets to re-import.", url));
    } else {
      if (data.model.version >= 2) {
        var parser = new pc.JsonModelParser(this._device);
        return parser.parse(data);
      }
    }
    return null;
  }, patch:function(asset, assets) {
    if (!asset.resource) {
      return;
    }
    var resource = asset.resource;
    var data = asset.data;
    resource.meshInstances.forEach(function(meshInstance, i) {
      if (data.mapping) {
        var handleMaterial = function(asset) {
          if (asset.resource) {
            meshInstance.material = asset.resource;
          } else {
            asset.once("load", handleMaterial);
            assets.load(asset);
          }
          asset.once("remove", function(asset) {
            if (meshInstance.material === asset.resource) {
              meshInstance.material = pc.ModelHandler.DEFAULT_MATERIAL;
            }
          });
        };
        if (!data.mapping[i]) {
          meshInstance.material = pc.ModelHandler.DEFAULT_MATERIAL;
          return;
        }
        var id = data.mapping[i].material;
        var url = data.mapping[i].path;
        var material;
        if (id !== undefined) {
          if (!id) {
            meshInstance.material = pc.ModelHandler.DEFAULT_MATERIAL;
          } else {
            material = assets.get(id);
            if (material) {
              handleMaterial(material);
            } else {
              assets.once("add:" + id, handleMaterial);
            }
          }
        } else {
          if (url !== undefined && url) {
            var fileUrl = asset.getFileUrl();
            var dirUrl = pc.path.getDirectory(fileUrl);
            var path = pc.path.join(dirUrl, data.mapping[i].path);
            material = assets.getByUrl(path);
            if (material) {
              handleMaterial(material);
            } else {
              assets.once("add:url:" + path, handleMaterial);
            }
          }
        }
      }
    });
  }};
  return {ModelHandler:ModelHandler};
}());
pc.extend(pc, function() {
  var ScriptHandler = function(app) {
    this._app = app;
    this._scripts = {};
    this._cache = {};
  };
  ScriptHandler._types = [];
  ScriptHandler._push = function(Type) {
    if (pc.script.legacy && ScriptHandler._types.length > 0) {
      console.assert("Script Ordering Error. Contact support@playcanvas.com");
    } else {
      ScriptHandler._types.push(Type);
    }
  };
  ScriptHandler.prototype = {load:function(url, callback) {
    var self = this;
    pc.script.app = this._app;
    this._loadScript(url, function(err, url, extra) {
      if (!err) {
        if (pc.script.legacy) {
          var Type = null;
          if (ScriptHandler._types.length) {
            Type = ScriptHandler._types.pop();
          }
          if (Type) {
            this._scripts[url] = Type;
          } else {
            Type = null;
          }
          callback(null, Type, extra);
        } else {
          var obj = {};
          for (var i = 0;i < ScriptHandler._types.length;i++) {
            obj[ScriptHandler._types[i].name] = ScriptHandler._types[i];
          }
          ScriptHandler._types.length = 0;
          callback(null, obj, extra);
          delete self._loader._cache[url + "script"];
        }
      } else {
        callback(err);
      }
    }.bind(this));
  }, open:function(url, data) {
    return data;
  }, patch:function(asset, assets) {
  }, _loadScript:function(url, callback) {
    var head = document.head;
    var element = document.createElement("script");
    this._cache[url] = element;
    element.async = false;
    element.addEventListener("error", function(e) {
      callback(pc.string.format("Script: {0} failed to load", e.target.src));
    }, false);
    var done = false;
    element.onload = element.onreadystatechange = function() {
      if (!done && (!this.readyState || (this.readyState == "loaded" || this.readyState == "complete"))) {
        done = true;
        callback(null, url, element);
      }
    };
    element.src = url;
    head.appendChild(element);
  }};
  return {ScriptHandler:ScriptHandler};
}());
pc.extend(pc, function() {
  var TextHandler = function() {
  };
  TextHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback(pc.string.format("Error loading text resource: {0} [{1}]", url, err));
      }
    });
  }, open:function(url, data) {
    return data;
  }, patch:function(asset, assets) {
  }};
  return {TextHandler:TextHandler};
}());
pc.extend(pc, function() {
  var BinaryHandler = function() {
  };
  BinaryHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, {responseType:pc.Http.ResponseType.ARRAY_BUFFER}, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback(pc.string.format("Error loading binary resource: {0} [{1}]", url, err));
      }
    });
  }, open:function(url, data) {
    return data;
  }, patch:function(asset, assets) {
  }};
  return {BinaryHandler:BinaryHandler};
}());
pc.extend(pc, function() {
  var JSON_ADDRESS_MODE = {"repeat":pc.ADDRESS_REPEAT, "clamp":pc.ADDRESS_CLAMP_TO_EDGE, "mirror":pc.ADDRESS_MIRRORED_REPEAT};
  var JSON_FILTER_MODE = {"nearest":pc.FILTER_NEAREST, "linear":pc.FILTER_LINEAR, "nearest_mip_nearest":pc.FILTER_NEAREST_MIPMAP_NEAREST, "linear_mip_nearest":pc.FILTER_LINEAR_MIPMAP_NEAREST, "nearest_mip_linear":pc.FILTER_NEAREST_MIPMAP_LINEAR, "linear_mip_linear":pc.FILTER_LINEAR_MIPMAP_LINEAR};
  function arrayBufferCopy(src, dst, dstByteOffset, numBytes) {
    var i;
    var dst32Offset = dstByteOffset / 4;
    var tail = numBytes % 4;
    var src32 = new Uint32Array(src.buffer, 0, (numBytes - tail) / 4);
    var dst32 = new Uint32Array(dst.buffer);
    for (i = 0;i < src32.length;i++) {
      dst32[dst32Offset + i] = src32[i];
    }
    for (i = numBytes - tail;i < numBytes;i++) {
      dst[dstByteOffset + i] = src[i];
    }
  }
  var TextureHandler = function(device, assets, loader) {
    this._device = device;
    this._assets = assets;
    this._loader = loader;
    this.crossOrigin = undefined;
    if (assets.prefix) {
      this.crossOrigin = "anonymous";
    }
  };
  TextureHandler.prototype = {load:function(url, callback) {
    var self = this;
    var urlWithoutParams = url.indexOf("?") >= 0 ? url.split("?")[0] : url;
    var ext = pc.path.getExtension(urlWithoutParams).toLowerCase();
    if (ext === ".dds" || ext === ".crn") {
      var options = {cache:true, responseType:"arraybuffer"};
      pc.http.get(url, options, function(err, response) {
        if (!err) {
          callback(null, response);
        } else {
          callback(err);
        }
      });
    } else {
      if (ext === ".jpg" || ext === ".jpeg" || ext === ".gif" || ext === ".png") {
        var image = new Image;
        if (self.crossOrigin !== undefined && pc.ABSOLUTE_URL.test(url)) {
          image.crossOrigin = self.crossOrigin;
        }
        image.onload = function() {
          callback(null, image);
        };
        image.onerror = function(event) {
          callback(pc.string.format("Error loading Texture from: '{0}'", url));
        };
        image.src = url;
      } else {
        setTimeout(function() {
          callback(pc.string.format("Error loading Texture: format not supported: '{0}'", ext));
        }, 0);
      }
    }
  }, open:function(url, data) {
    if (!url) {
      return;
    }
    var self = this;
    var texture;
    var ext = pc.path.getExtension(url).toLowerCase();
    var format = null;
    if (data instanceof Image || data instanceof HTMLImageElement) {
      var img = data;
      format = ext === ".jpg" || ext === ".jpeg" ? pc.PIXELFORMAT_R8_G8_B8 : pc.PIXELFORMAT_R8_G8_B8_A8;
      texture = new pc.Texture(this._device, {width:img.width, height:img.height, format:format});
      texture.setSource(img);
    } else {
      if (data instanceof ArrayBuffer) {
        if (ext === ".crn") {
          var srcSize = data.byteLength;
          var bytes = new Uint8Array(data);
          var src = Module._malloc(srcSize);
          arrayBufferCopy(bytes, Module.HEAPU8, src, srcSize);
          var dst = Module._crn_decompress_get_data(src, srcSize);
          var dstSize = Module._crn_decompress_get_size(src, srcSize);
          data = Module.HEAPU8.buffer.slice(dst, dst + dstSize);
        }
        var header = new Uint32Array(data, 0, 128 / 4);
        var width = header[4];
        var height = header[3];
        var mips = Math.max(header[7], 1);
        var isFourCc = header[20] === 4;
        var fcc = header[21];
        var bpp = header[22];
        var isCubemap = header[28] === 65024;
        var FCC_DXT1 = 827611204;
        var FCC_DXT5 = 894720068;
        var FCC_FP32 = 116;
        var FCC_ETC1 = 826496069;
        var FCC_PVRTC_2BPP_RGB_1 = 825438800;
        var FCC_PVRTC_2BPP_RGBA_1 = 825504336;
        var FCC_PVRTC_4BPP_RGB_1 = 825439312;
        var FCC_PVRTC_4BPP_RGBA_1 = 825504848;
        var compressed = false;
        var floating = false;
        var etc1 = false;
        var pvrtc2 = false;
        var pvrtc4 = false;
        if (isFourCc) {
          if (fcc === FCC_DXT1) {
            format = pc.PIXELFORMAT_DXT1;
            compressed = true;
          } else {
            if (fcc === FCC_DXT5) {
              format = pc.PIXELFORMAT_DXT5;
              compressed = true;
            } else {
              if (fcc === FCC_FP32) {
                format = pc.PIXELFORMAT_RGBA32F;
                floating = true;
              } else {
                if (fcc === FCC_ETC1) {
                  format = pc.PIXELFORMAT_ETC1;
                  compressed = true;
                  etc1 = true;
                } else {
                  if (fcc === FCC_PVRTC_2BPP_RGB_1 || fcc === FCC_PVRTC_2BPP_RGBA_1) {
                    format = fcc === FCC_PVRTC_2BPP_RGB_1 ? pc.PIXELFORMAT_PVRTC_2BPP_RGB_1 : pc.PIXELFORMAT_PVRTC_2BPP_RGBA_1;
                    compressed = true;
                    pvrtc2 = true;
                  } else {
                    if (fcc === FCC_PVRTC_4BPP_RGB_1 || fcc === FCC_PVRTC_4BPP_RGBA_1) {
                      format = fcc === FCC_PVRTC_4BPP_RGB_1 ? pc.PIXELFORMAT_PVRTC_4BPP_RGB_1 : pc.PIXELFORMAT_PVRTC_4BPP_RGBA_1;
                      compressed = true;
                      pvrtc4 = true;
                    }
                  }
                }
              }
            }
          }
        } else {
          if (bpp === 32) {
            format = pc.PIXELFORMAT_R8_G8_B8_A8;
          }
        }
        if (!format) {
          texture = new pc.Texture(this._device, {width:4, height:4, format:pc.PIXELFORMAT_R8_G8_B8});
          return texture;
        }
        var texOptions = {width:width, height:height, format:format, cubemap:isCubemap};
        texture = new pc.Texture(this._device, texOptions);
        if (isCubemap) {
          texture.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
          texture.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
        }
        var offset = 128;
        var faces = isCubemap ? 6 : 1;
        var mipSize;
        var DXT_BLOCK_WIDTH = 4;
        var DXT_BLOCK_HEIGHT = 4;
        var blockSize = fcc === FCC_DXT1 ? 8 : 16;
        var numBlocksAcross, numBlocksDown, numBlocks;
        for (var face = 0;face < faces;face++) {
          var mipWidth = width;
          var mipHeight = height;
          for (var i = 0;i < mips;i++) {
            if (compressed) {
              if (etc1) {
                mipSize = Math.floor((mipWidth + 3) / 4) * Math.floor((mipHeight + 3) / 4) * 8;
              } else {
                if (pvrtc2) {
                  mipSize = Math.max(mipWidth, 16) * Math.max(mipHeight, 8) / 4;
                } else {
                  if (pvrtc4) {
                    mipSize = Math.max(mipWidth, 8) * Math.max(mipHeight, 8) / 2;
                  } else {
                    numBlocksAcross = Math.floor((mipWidth + DXT_BLOCK_WIDTH - 1) / DXT_BLOCK_WIDTH);
                    numBlocksDown = Math.floor((mipHeight + DXT_BLOCK_HEIGHT - 1) / DXT_BLOCK_HEIGHT);
                    numBlocks = numBlocksAcross * numBlocksDown;
                    mipSize = numBlocks * blockSize;
                  }
                }
              }
            } else {
              mipSize = mipWidth * mipHeight * 4;
            }
            var mipBuff = floating ? new Float32Array(data, offset, mipSize) : new Uint8Array(data, offset, mipSize);
            if (!isCubemap) {
              texture._levels[i] = mipBuff;
            } else {
              if (!texture._levels[i]) {
                texture._levels[i] = [];
              }
              texture._levels[i][face] = mipBuff;
            }
            offset += floating ? mipSize * 4 : mipSize;
            mipWidth = Math.max(mipWidth * .5, 1);
            mipHeight = Math.max(mipHeight * .5, 1);
          }
        }
        texture.upload();
      }
    }
    return texture;
  }, patch:function(asset, assets) {
    var texture = asset.resource;
    if (!texture) {
      return;
    }
    if (texture.name !== asset.name) {
      texture.name = asset.name;
    }
    if (asset.data.hasOwnProperty("minfilter") && texture.minFilter !== JSON_FILTER_MODE[asset.data.minfilter]) {
      texture.minFilter = JSON_FILTER_MODE[asset.data.minfilter];
    }
    if (asset.data.hasOwnProperty("magfilter") && texture.magFilter !== JSON_FILTER_MODE[asset.data.magfilter]) {
      texture.magFilter = JSON_FILTER_MODE[asset.data.magfilter];
    }
    if (asset.data.hasOwnProperty("addressu") && texture.addressU !== JSON_ADDRESS_MODE[asset.data.addressu]) {
      texture.addressU = JSON_ADDRESS_MODE[asset.data.addressu];
    }
    if (asset.data.hasOwnProperty("addressv") && texture.addressV !== JSON_ADDRESS_MODE[asset.data.addressv]) {
      texture.addressV = JSON_ADDRESS_MODE[asset.data.addressv];
    }
    if (asset.data.hasOwnProperty("mipmaps") && texture.mipmaps !== asset.data.mipmaps) {
      texture.mipmaps = asset.data.mipmaps;
    }
    if (asset.data.hasOwnProperty("anisotropy") && texture.anisotropy !== asset.data.anisotropy) {
      texture.anisotropy = asset.data.anisotropy;
    }
    if (asset.data.hasOwnProperty("rgbm") && texture.rgbm !== !!asset.data.rgbm) {
      texture.rgbm = !!asset.data.rgbm;
    }
  }};
  return {TextureHandler:TextureHandler};
}());
pc.extend(pc, function() {
  var HtmlHandler = function() {
  };
  HtmlHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback(pc.string.format("Error loading html resource: {0} [{1}]", url, err));
      }
    });
  }, open:function(url, data) {
    return data;
  }, patch:function(asset, assets) {
  }};
  return {HtmlHandler:HtmlHandler};
}());
pc.extend(pc, function() {
  var CssHandler = function() {
  };
  CssHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback(pc.string.format("Error loading css resource: {0} [{1}]", url, err));
      }
    });
  }, open:function(url, data) {
    return data;
  }, patch:function(asset, assets) {
  }};
  var createStyle = function(cssString) {
    var result = document.createElement("style");
    result.type = "text/css";
    if (result.styleSheet) {
      result.styleSheet.cssText = cssString;
    } else {
      result.appendChild(document.createTextNode(cssString));
    }
    return result;
  };
  return {CssHandler:CssHandler, createStyle:createStyle};
}());
pc.extend(pc, function() {
  var ShaderHandler = function() {
  };
  ShaderHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback(pc.string.format("Error loading shader resource: {0} [{1}]", url, err));
      }
    });
  }, open:function(url, data) {
    return data;
  }, patch:function(asset, assets) {
  }};
  return {ShaderHandler:ShaderHandler};
}());
pc.extend(pc, function() {
  var SceneHandler = function(app) {
    this._app = app;
  };
  SceneHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback("Error requesting scene: " + url);
      }
    });
  }, open:function(url, data) {
    this._app.systems.script.preloading = true;
    var parser = new pc.SceneParser(this._app);
    var parent = parser.parse(data);
    var scene = this._app.scene;
    scene.root = parent;
    this._app.applySceneSettings(data.settings);
    this._app.systems.script.preloading = false;
    return scene;
  }, patch:function(asset, assets) {
  }};
  return {SceneHandler:SceneHandler};
}());
pc.extend(pc, function() {
  var HierarchyHandler = function(app) {
    this._app = app;
  };
  HierarchyHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback("Error requesting scene: " + url);
      }
    });
  }, open:function(url, data) {
    this._app.systems.script.preloading = true;
    var parser = new pc.SceneParser(this._app);
    var parent = parser.parse(data);
    this._app.systems.script.preloading = false;
    return parent;
  }};
  return {HierarchyHandler:HierarchyHandler};
}());
pc.extend(pc, function() {
  var SceneSettingsHandler = function(app) {
    this._app = app;
  };
  SceneSettingsHandler.prototype = {load:function(url, callback) {
    pc.http.get(url, function(err, response) {
      if (!err) {
        callback(null, response);
      } else {
        callback("Error requesting scene: " + url);
      }
    });
  }, open:function(url, data) {
    return data.settings;
  }};
  return {SceneSettingsHandler:SceneSettingsHandler};
}());
pc.extend(pc, function() {
  var FolderHandler = function() {
  };
  FolderHandler.prototype = {load:function(url, callback) {
    callback(null, null);
  }, open:function(url, data) {
    return data;
  }};
  return {FolderHandler:FolderHandler};
}());
pc.extend(pc, function() {
  var FontHandler = function(loader) {
    this._loader = loader;
  };
  FontHandler.prototype = {load:function(url, callback) {
    var self = this;
    if (pc.path.getExtension(url) === ".json") {
      pc.http.get(url, function(err, response) {
        if (!err) {
          var textureUrl = url.replace(".json", ".png");
          self._loader.load(textureUrl, "texture", function(err, texture) {
            if (err) {
              callback(err);
            } else {
              callback(null, {data:response, texture:texture});
            }
          });
        } else {
          callback(pc.string.format("Error loading css resource: {0} [{1}]", url, err));
        }
      });
    } else {
      this._loader.load(url, "texture", function(err, texture) {
        callback(null, texture);
      });
    }
  }, open:function(url, data) {
    var font;
    if (data.texture) {
      font = new pc.Font(data.texture, data.data);
    } else {
      font = new pc.Font(data, null);
    }
    return font;
  }, patch:function(asset, assets) {
    var font = asset.resource;
    if (!font.data && asset.data) {
      font.data = asset.data;
    } else {
      if (!asset.data && font.data) {
        asset.data = font.data;
      }
    }
  }};
  return {FontHandler:FontHandler};
}());
pc.extend(pc, function() {
  var JSON_PRIMITIVE_TYPE = {"points":pc.PRIMITIVE_POINTS, "lines":pc.PRIMITIVE_LINES, "lineloop":pc.PRIMITIVE_LINELOOP, "linestrip":pc.PRIMITIVE_LINESTRIP, "triangles":pc.PRIMITIVE_TRIANGLES, "trianglestrip":pc.PRIMITIVE_TRISTRIP, "trianglefan":pc.PRIMITIVE_TRIFAN};
  var JSON_VERTEX_ELEMENT_TYPE = {"int8":pc.ELEMENTTYPE_INT8, "uint8":pc.ELEMENTTYPE_UINT8, "int16":pc.ELEMENTTYPE_INT16, "uint16":pc.ELEMENTTYPE_UINT16, "int32":pc.ELEMENTTYPE_INT32, "uint32":pc.ELEMENTTYPE_UINT32, "float32":pc.ELEMENTTYPE_FLOAT32};
  var JsonModelParser = function(device) {
    this._device = device;
  };
  JsonModelParser.prototype = {parse:function(data) {
    var modelData = data.model;
    var nodes = this._parseNodes(data);
    var skins = this._parseSkins(data, nodes);
    var vertexBuffers = this._parseVertexBuffers(data);
    var indices = this._parseIndexBuffers(data, vertexBuffers);
    var meshes = this._parseMeshes(data, skins.skins, vertexBuffers, indices.buffer, indices.data);
    var meshInstances = this._parseMeshInstances(data, nodes, meshes, skins.skins, skins.instances);
    var model = new pc.Model;
    model.graph = nodes[0];
    model.meshInstances = meshInstances;
    model.skinInstances = skins.instances;
    model.getGraph().syncHierarchy();
    return model;
  }, _parseNodes:function(data) {
    var modelData = data.model;
    var nodes = [];
    var i;
    for (i = 0;i < modelData.nodes.length;i++) {
      var nodeData = modelData.nodes[i];
      var node = new pc.GraphNode;
      node.setName(nodeData.name);
      node.setLocalPosition(nodeData.position[0], nodeData.position[1], nodeData.position[2]);
      node.setLocalEulerAngles(nodeData.rotation[0], nodeData.rotation[1], nodeData.rotation[2]);
      node.setLocalScale(nodeData.scale[0], nodeData.scale[1], nodeData.scale[2]);
      nodes.push(node);
    }
    for (i = 1;i < modelData.parents.length;i++) {
      nodes[modelData.parents[i]].addChild(nodes[i]);
    }
    return nodes;
  }, _parseSkins:function(data, nodes) {
    var modelData = data.model;
    var skins = [];
    var skinInstances = [];
    var i, j;
    if (!this._device.supportsBoneTextures && modelData.skins.length > 0) {
      var boneLimit = this._device.getBoneLimit();
      pc.partitionSkin(modelData, null, boneLimit);
    }
    for (i = 0;i < modelData.skins.length;i++) {
      var skinData = modelData.skins[i];
      var inverseBindMatrices = [];
      for (j = 0;j < skinData.inverseBindMatrices.length;j++) {
        var ibm = skinData.inverseBindMatrices[j];
        inverseBindMatrices[j] = new pc.Mat4(ibm[0], ibm[1], ibm[2], ibm[3], ibm[4], ibm[5], ibm[6], ibm[7], ibm[8], ibm[9], ibm[10], ibm[11], ibm[12], ibm[13], ibm[14], ibm[15]);
      }
      var skin = new pc.Skin(this._device, inverseBindMatrices, skinData.boneNames);
      skins.push(skin);
      var skinInstance = new pc.SkinInstance(skin, nodes[0]);
      var bones = [];
      for (j = 0;j < skin.boneNames.length;j++) {
        var boneName = skin.boneNames[j];
        var bone = nodes[0].findByName(boneName);
        bones.push(bone);
      }
      skinInstance.bones = bones;
      skinInstances.push(skinInstance);
    }
    return {skins:skins, instances:skinInstances};
  }, _parseVertexBuffers:function(data) {
    var modelData = data.model;
    var vertexBuffers = [];
    var attribute, attributeName;
    var attributeMap = {position:pc.SEMANTIC_POSITION, normal:pc.SEMANTIC_NORMAL, tangent:pc.SEMANTIC_TANGENT, blendWeight:pc.SEMANTIC_BLENDWEIGHT, blendIndices:pc.SEMANTIC_BLENDINDICES, color:pc.SEMANTIC_COLOR, texCoord0:pc.SEMANTIC_TEXCOORD0, texCoord1:pc.SEMANTIC_TEXCOORD1, texCoord2:pc.SEMANTIC_TEXCOORD2, texCoord3:pc.SEMANTIC_TEXCOORD3, texCoord4:pc.SEMANTIC_TEXCOORD4, texCoord5:pc.SEMANTIC_TEXCOORD5, texCoord6:pc.SEMANTIC_TEXCOORD6, texCoord7:pc.SEMANTIC_TEXCOORD7};
    var i, j;
    for (i = 0;i < modelData.vertices.length;i++) {
      var vertexData = modelData.vertices[i];
      if (vertexData.position && vertexData.normal && vertexData.texCoord0) {
        var indices = [];
        for (j = 0;j < modelData.meshes.length;j++) {
          if (modelData.meshes[j].vertices === i) {
            indices = indices.concat(modelData.meshes[j].indices);
          }
        }
        var tangents = pc.calculateTangents(vertexData.position.data, vertexData.normal.data, vertexData.texCoord0.data, indices);
        vertexData.tangent = {type:"float32", components:4, data:tangents};
      }
      var formatDesc = [];
      for (attributeName in vertexData) {
        attribute = vertexData[attributeName];
        var attribType = attribute.type;
        if (!this._device.supportsUnsignedByte) {
          if (attribType === "uint8") {
            attribType = "float32";
          }
          if (attribType === "int8") {
            attribType = "float32";
          }
        }
        formatDesc.push({semantic:attributeMap[attributeName], components:attribute.components, type:JSON_VERTEX_ELEMENT_TYPE[attribType], normalize:attributeMap[attributeName] === pc.SEMANTIC_COLOR});
      }
      var vertexFormat = new pc.VertexFormat(this._device, formatDesc);
      var numVertices = vertexData.position.data.length / vertexData.position.components;
      var vertexBuffer = new pc.VertexBuffer(this._device, vertexFormat, numVertices);
      var iterator = new pc.VertexIterator(vertexBuffer);
      for (j = 0;j < numVertices;j++) {
        for (attributeName in vertexData) {
          attribute = vertexData[attributeName];
          switch(attribute.components) {
            case 1:
              iterator.element[attributeMap[attributeName]].set(attribute.data[j]);
              break;
            case 2:
              iterator.element[attributeMap[attributeName]].set(attribute.data[j * 2], attribute.data[j * 2 + 1]);
              break;
            case 3:
              iterator.element[attributeMap[attributeName]].set(attribute.data[j * 3], attribute.data[j * 3 + 1], attribute.data[j * 3 + 2]);
              break;
            case 4:
              iterator.element[attributeMap[attributeName]].set(attribute.data[j * 4], attribute.data[j * 4 + 1], attribute.data[j * 4 + 2], attribute.data[j * 4 + 3]);
              break;
          }
        }
        iterator.next();
      }
      iterator.end();
      vertexBuffers.push(vertexBuffer);
    }
    return vertexBuffers;
  }, _parseIndexBuffers:function(data, vertexBuffers) {
    var modelData = data.model;
    var indexBuffer = null;
    var indexData = null;
    var i;
    var numIndices = 0;
    for (i = 0;i < modelData.meshes.length;i++) {
      var meshData = modelData.meshes[i];
      if (meshData.indices !== undefined) {
        numIndices += meshData.indices.length;
      }
    }
    var maxVerts = 0;
    for (i = 0;i < vertexBuffers.length;i++) {
      maxVerts = Math.max(maxVerts, vertexBuffers[i].numVertices);
    }
    if (numIndices > 0) {
      if (maxVerts > 65535 && this._device.extUintElement) {
        indexBuffer = new pc.IndexBuffer(this._device, pc.INDEXFORMAT_UINT32, numIndices);
        indexData = new Uint32Array(indexBuffer.lock());
      } else {
        indexBuffer = new pc.IndexBuffer(this._device, pc.INDEXFORMAT_UINT16, numIndices);
        indexData = new Uint16Array(indexBuffer.lock());
      }
    }
    return {buffer:indexBuffer, data:indexData};
  }, _parseMeshes:function(data, skins, vertexBuffers, indexBuffer, indexData) {
    var modelData = data.model;
    var meshes = [];
    var indexBase = 0;
    var i;
    for (i = 0;i < modelData.meshes.length;i++) {
      var meshData = modelData.meshes[i];
      var meshAabb = meshData.aabb;
      var min = meshAabb.min;
      var max = meshAabb.max;
      var aabb = new pc.BoundingBox(new pc.Vec3((max[0] + min[0]) * .5, (max[1] + min[1]) * .5, (max[2] + min[2]) * .5), new pc.Vec3((max[0] - min[0]) * .5, (max[1] - min[1]) * .5, (max[2] - min[2]) * .5));
      var indexed = meshData.indices !== undefined;
      var mesh = new pc.Mesh;
      mesh.vertexBuffer = vertexBuffers[meshData.vertices];
      mesh.indexBuffer[0] = indexed ? indexBuffer : null;
      mesh.primitive[0].type = JSON_PRIMITIVE_TYPE[meshData.type];
      mesh.primitive[0].base = indexed ? meshData.base + indexBase : meshData.base;
      mesh.primitive[0].count = meshData.count;
      mesh.primitive[0].indexed = indexed;
      mesh.skin = meshData.skin !== undefined ? skins[meshData.skin] : null;
      mesh.aabb = aabb;
      if (indexed) {
        indexData.set(meshData.indices, indexBase);
        indexBase += meshData.indices.length;
      }
      meshes.push(mesh);
    }
    if (indexBuffer !== null) {
      indexBuffer.unlock();
    }
    return meshes;
  }, _parseMeshInstances:function(data, nodes, meshes, skins, skinInstances) {
    var modelData = data.model;
    var meshInstances = [];
    var i;
    for (i = 0;i < modelData.meshInstances.length;i++) {
      var meshInstanceData = modelData.meshInstances[i];
      var node = nodes[meshInstanceData.node];
      var mesh = meshes[meshInstanceData.mesh];
      var meshInstance = new pc.MeshInstance(node, mesh, pc.ModelHandler.DEFAULT_MATERIAL);
      if (mesh.skin) {
        var skinIndex = skins.indexOf(mesh.skin);
        if (skinIndex === -1) {
          throw new Error("Mesh's skin does not appear in skin array.");
        }
        meshInstance.skinInstance = skinInstances[skinIndex];
      }
      meshInstances.push(meshInstance);
    }
    return meshInstances;
  }};
  return {JsonModelParser:JsonModelParser};
}());
pc.extend(pc, function() {
  var SceneParser = function(app) {
    this._app = app;
  };
  SceneParser.prototype = {parse:function(data) {
    var entities = {};
    var id, i;
    var parent = null;
    for (id in data.entities) {
      entities[id] = this._createEntity(data.entities[id]);
      if (data.entities[id].parent === null) {
        parent = entities[id];
      }
    }
    for (id in data.entities) {
      var entity = entities[id];
      var l = data.entities[id].children.length;
      for (i = 0;i < l;i++) {
        var resource_id = data.entities[id].children[i];
        if (entities[resource_id]) {
          entities[id].addChild(entities[resource_id]);
        }
      }
    }
    this._openComponentData(parent, data.entities);
    return parent;
  }, _createEntity:function(data) {
    var entity = new pc.Entity;
    var p = data.position;
    var r = data.rotation;
    var s = data.scale;
    entity.name = data.name;
    entity._guid = data.resource_id;
    entity.setLocalPosition(p[0], p[1], p[2]);
    entity.setLocalEulerAngles(r[0], r[1], r[2]);
    entity.setLocalScale(s[0], s[1], s[2]);
    entity._enabled = data.enabled !== undefined ? data.enabled : true;
    entity._enabledInHierarchy = entity._enabled;
    entity.template = data.template;
    if (data.tags) {
      for (var i = 0;i < data.tags.length;i++) {
        entity.tags.add(data.tags[i]);
      }
    }
    if (data.labels) {
      data.labels.forEach(function(label) {
        entity.addLabel(label);
      });
    }
    return entity;
  }, _openComponentData:function(entity, entities) {
    var systems = this._app.systems.list();
    var i, len = systems.length;
    var edata = entities[entity._guid];
    for (i = 0;i < len;i++) {
      var componentData = edata.components[systems[i].id];
      if (componentData) {
        this._app.systems[systems[i].id].addComponent(entity, componentData);
      }
    }
    var child, length = edata.children.length;
    var children = entity._children;
    for (i = 0;i < length;i++) {
      children[i] = this._openComponentData(children[i], entities);
    }
    return entity;
  }};
  return {SceneParser:SceneParser};
}());
pc.extend(pc, function() {
  var assetIdCounter = 0;
  var ABSOLUTE_URL = new RegExp("^" + "\\s*" + "(?:" + "[a-z]+[a-z0-9\\-\\+\\.]*" + ":" + ")?" + "//", "i");
  var Asset = function(name, type, file, data) {
    this._id = ++assetIdCounter;
    this.name = name || "";
    this.type = type;
    this.tags = new pc.Tags(this);
    this._preload = false;
    this.variants = new pc.AssetVariants(this);
    this._file = null;
    this._data = data || {};
    this._resources = [];
    this.loaded = false;
    this.loading = false;
    this.registry = null;
    pc.events.attach(this);
    if (file) {
      this.file = file;
    }
  };
  Asset.prototype = {getFileUrl:function() {
    var file = this.getPreferredFile();
    if (!file || !file.url) {
      return null;
    }
    var url = file.url;
    if (this.registry && this.registry.prefix && !ABSOLUTE_URL.test(url)) {
      url = this.registry.prefix + url;
    }
    if (this.type !== "script" && file.hash) {
      var separator = url.indexOf("?") !== -1 ? "&" : "?";
      url += separator + "t=" + file.hash;
    }
    return url;
  }, getPreferredFile:function() {
    if (!this.file) {
      return null;
    }
    if (this.type === "texture") {
      var device = this.registry._loader.getHandler("texture")._device;
      if (this.variants.pvr && device.extCompressedTexturePVRTC) {
        return this.variants.pvr;
      } else {
        if (this.variants.dxt && device.extCompressedTextureS3TC) {
          return this.variants.dxt;
        } else {
          if (this.variants.etc1 && device.extCompressedTextureETC1) {
            return this.variants.etc1;
          }
        }
      }
    }
    return this.file;
  }, ready:function(callback, scope) {
    scope = scope || this;
    if (this.resource) {
      callback.call(scope, this);
    } else {
      this.once("load", function(asset) {
        callback.call(scope, asset);
      });
    }
  }, reload:function() {
    if (!this.loaded) {
      return;
    }
    if (this.type === "cubemap") {
      this.registry._loader.patch(this, this.registry);
    } else {
      this.loaded = false;
      this.registry.load(this);
    }
  }, unload:function() {
    if (!this.loaded && !this.resource) {
      return;
    }
    this.fire("unload", this);
    this.registry.fire("unload:" + this.id, this);
    if (this.resource && this.resource.destroy) {
      this.resource.destroy();
    }
    this.resource = null;
    this.loaded = false;
    if (this.file) {
      this.registry._loader.clearCache(this.getFileUrl(), this.type);
    }
  }};
  Object.defineProperty(Asset.prototype, "id", {get:function() {
    return this._id;
  }, set:function(value) {
    this._id = value;
    if (value > assetIdCounter) {
      assetIdCounter = value;
    }
  }});
  Object.defineProperty(Asset.prototype, "file", {get:function() {
    return this._file;
  }, set:function(value) {
    if (!!value !== !!this._file || value && this._file && value.hash !== this._file) {
      if (value) {
        if (!this._file) {
          this._file = {};
        }
        this._file.url = value.url;
        this._file.filename = value.filename;
        this._file.hash = value.hash;
        this._file.size = value.size;
        this._file.variants = this.variants;
        if (value.hasOwnProperty("variants")) {
          this.variants.clear();
          if (value.variants) {
            for (var key in value.variants) {
              if (!value.variants[key]) {
                continue;
              }
              this.variants[key] = value.variants[key];
            }
          }
        }
        this.fire("change", this, "file", this._file, this._file);
        this.reload();
      } else {
        this._file = null;
        this.variants.clear();
      }
    } else {
      if (value && this._file && value.hasOwnProperty("variants")) {
        this.variants.clear();
        if (value.variants) {
          for (var key in value.variants) {
            if (!value.variants[key]) {
              continue;
            }
            this.variants[key] = value.variants[key];
          }
        }
      }
    }
  }});
  Object.defineProperty(Asset.prototype, "data", {get:function() {
    return this._data;
  }, set:function(value) {
    var old = this._data;
    this._data = value;
    if (value !== old) {
      this.fire("change", this, "data", value, old);
      if (this.loaded) {
        this.registry._loader.patch(this, this.registry);
      }
    }
  }});
  Object.defineProperty(Asset.prototype, "resource", {get:function() {
    return this._resources[0];
  }, set:function(value) {
    var _old = this._resources[0];
    this._resources[0] = value;
    this.fire("change", this, "resource", value, _old);
  }});
  Object.defineProperty(Asset.prototype, "resources", {get:function() {
    return this._resources;
  }, set:function(value) {
    var _old = this._resources;
    this._resources = value;
    this.fire("change", this, "resources", value, _old);
  }});
  Object.defineProperty(Asset.prototype, "preload", {get:function() {
    return this._preload;
  }, set:function(value) {
    if (this._preload === !!value) {
      return;
    }
    this._preload = value;
    if (this._preload && !this.loaded && !this.loading && this.registry) {
      this.registry.load(this);
    }
  }});
  return {Asset:Asset, ASSET_ANIMATION:"animation", ASSET_AUDIO:"audio", ASSET_IMAGE:"image", ASSET_JSON:"json", ASSET_MODEL:"model", ASSET_MATERIAL:"material", ASSET_TEXT:"text", ASSET_TEXTURE:"texture", ASSET_CUBEMAP:"cubemap", ASSET_SHADER:"shader", ASSET_CSS:"css", ASSET_HTML:"html", ASSET_SCRIPT:"script", ABSOLUTE_URL:ABSOLUTE_URL};
}());
pc.extend(pc, function() {
  var properties = [];
  var AssetVariants = function(asset) {
    this.asset = asset;
  };
  var defineVariantProperty = function(name) {
    var field = "_" + name;
    properties.push(field);
    Object.defineProperty(AssetVariants.prototype, name, {get:function() {
      return this[field] || null;
    }, set:function(value) {
      if (!!this[field] !== !!value || this[field] && value && this[field].hash !== value.hash) {
        var old = this[field];
        if (value) {
          this[field] = {url:value.url, filename:value.filename, size:value.size, hash:value.hash, opt:value.opt || 0};
        } else {
          this[field] = null;
        }
        if (this.asset.file) {
          this.asset.fire("change", this.asset, "file", this.asset._file, this.asset._file);
          this.asset.reload();
        }
      }
    }});
  };
  defineVariantProperty("dxt");
  defineVariantProperty("pvr");
  defineVariantProperty("etc1");
  AssetVariants.prototype.clear = function() {
    for (var i = 0;i < properties.length;i++) {
      this[properties[i]] = null;
    }
  };
  return {AssetVariants:AssetVariants};
}());
pc.extend(pc, function() {
  var AssetRegistry = function(loader) {
    this._loader = loader;
    this._assets = [];
    this._cache = {};
    this._names = {};
    this._tags = new pc.TagsCache("_id");
    this._urls = {};
    this.prefix = null;
    pc.extend(this, pc.events);
  };
  AssetRegistry.prototype = {list:function(filters) {
    filters = filters || {};
    return this._assets.filter(function(asset) {
      var include = true;
      if (filters.preload !== undefined) {
        include = asset.preload === filters.preload;
      }
      return include;
    });
  }, add:function(asset) {
    var index = this._assets.push(asset) - 1;
    var url;
    this._cache[asset.id] = index;
    if (!this._names[asset.name]) {
      this._names[asset.name] = [];
    }
    this._names[asset.name].push(index);
    if (asset.file) {
      url = asset.file.url;
      this._urls[url] = index;
    }
    asset.registry = this;
    this._tags.addItem(asset);
    asset.tags.on("add", this._onTagAdd, this);
    asset.tags.on("remove", this._onTagRemove, this);
    this.fire("add", asset);
    this.fire("add:" + asset.id, asset);
    if (url) {
      this.fire("add:url:" + url, asset);
    }
    if (asset.preload) {
      this.load(asset);
    }
  }, remove:function(asset) {
    delete this._cache[asset.id];
    delete this._names[asset.name];
    var url = asset.file ? asset.file.url : null;
    if (url) {
      delete this._urls[url];
    }
    this._tags.removeItem(asset);
    asset.tags.off("add", this._onTagAdd, this);
    asset.tags.off("remove", this._onTagRemove, this);
    asset.fire("remove", asset);
    this.fire("remove", asset);
    this.fire("remove:" + asset.id, asset);
    if (url) {
      this.fire("remove:url:" + url, asset);
    }
  }, get:function(id) {
    var idx = this._cache[id];
    return this._assets[idx];
  }, getByUrl:function(url) {
    var idx = this._urls[url];
    return this._assets[idx];
  }, load:function(asset) {
    if (asset.loading) {
      return;
    }
    var self = this;
    if (asset.loaded) {
      if (asset.type === "cubemap") {
        self._loader.patch(asset, this);
      }
      return;
    }
    var load = !!asset.file;
    var file = asset.getPreferredFile();
    var _load = function() {
      var url = asset.getFileUrl();
      asset.loading = true;
      self._loader.load(url, asset.type, function(err, resource, extra) {
        asset.loaded = true;
        asset.loading = false;
        if (err) {
          self.fire("error", err, asset);
          self.fire("error:" + asset.id, err, asset);
          asset.fire("error", err, asset);
          return;
        }
        if (resource instanceof Array) {
          asset.resources = resource;
        } else {
          asset.resource = resource;
        }
        if (!pc.script.legacy && asset.type === "script") {
          var loader = self._loader.getHandler("script");
          if (loader._cache[asset.id] && loader._cache[asset.id].parentNode === document.head) {
            document.head.removeChild(loader._cache[asset.id]);
          }
          loader._cache[asset.id] = extra;
        }
        self._loader.patch(asset, self);
        self.fire("load", asset);
        self.fire("load:" + asset.id, asset);
        if (file && file.url) {
          self.fire("load:url:" + file.url, asset);
        }
        asset.fire("load", asset);
      });
    };
    var _open = function() {
      var resource = self._loader.open(asset.type, asset.data);
      if (resource instanceof Array) {
        asset.resources = resource;
      } else {
        asset.resource = resource;
      }
      asset.loaded = true;
      self._loader.patch(asset, self);
      self.fire("load", asset);
      self.fire("load:" + asset.id, asset);
      if (file && file.url) {
        self.fire("load:url:" + file.url, asset);
      }
      asset.fire("load", asset);
    };
    if (file && asset.type === "cubemap") {
      load = false;
      var url = asset.getFileUrl();
      this._loader.load(url, "texture", function(err, texture) {
        if (!err) {
          self._loader.patch({resource:texture, type:"texture", data:asset.data}, self);
          asset._dds = texture;
          _open();
        } else {
          self.fire("error", err, asset);
          self.fire("error:" + asset.id, err, asset);
          asset.fire("error", err, asset);
          return;
        }
      });
    }
    if (!file) {
      _open();
    } else {
      if (load) {
        this.fire("load:start", asset);
        this.fire("load:" + asset.id + ":start", asset);
        _load();
      }
    }
  }, loadFromUrl:function(url, type, callback) {
    var self = this;
    var name = pc.path.getBasename(url);
    var file = {url:url};
    var data = {};
    var asset = self.getByUrl(url);
    if (!asset) {
      asset = new pc.Asset(name, type, file, data);
      self.add(asset);
    }
    if (type === "model") {
      self._loadModel(asset, callback);
      return;
    }
    asset.once("load", function(asset) {
      callback(null, asset);
    });
    asset.once("error", function(err) {
      callback(err);
    });
    self.load(asset);
  }, _loadModel:function(asset, callback) {
    var self = this;
    var url = asset.getFileUrl();
    var dir = pc.path.getDirectory(url);
    var basename = pc.path.getBasename(url);
    var name = basename.replace(".json", "");
    var mappingUrl = pc.path.join(dir, basename.replace(".json", ".mapping.json"));
    var _loadAsset = function(asset) {
      asset.once("load", function(asset) {
        callback(null, asset);
      });
      asset.once("error", function(err) {
        callback(err);
      });
      self.load(asset);
    };
    this._loader.load(mappingUrl, "json", function(err, data) {
      if (err) {
        asset.data = {mapping:[]};
        _loadAsset(asset);
        return;
      }
      self._loadMaterials(dir, data, function(err, materials) {
        asset.data = data;
        _loadAsset(asset);
      });
    });
  }, _loadMaterials:function(dir, mapping, callback) {
    var self = this;
    var i;
    var count = mapping.mapping.length;
    var materials = [];
    var done = function(err, materials) {
      self._loadTextures(materials, function(err, textures) {
        callback(null, materials);
      });
    };
    if (count === 0) {
      callback(null, materials);
    }
    var onLoadAsset = function(err, asset) {
      materials.push(asset);
      count--;
      if (count === 0) {
        done(null, materials);
      }
    };
    for (i = 0;i < mapping.mapping.length;i++) {
      var path = mapping.mapping[i].path;
      if (path) {
        self.loadFromUrl(pc.path.join(dir, path), "material", onLoadAsset);
      } else {
        count--;
      }
    }
  }, _loadTextures:function(materials, callback) {
    var self = this;
    var i, j;
    var used = {};
    var urls = [];
    var textures = [];
    var count = 0;
    for (i = 0;i < materials.length;i++) {
      if (materials[i].data.parameters) {
        var params = materials[i].data.parameters;
        for (j = 0;j < params.length;j++) {
          if (params[j].type === "texture") {
            var url = materials[i].getFileUrl();
            var dir = pc.path.getDirectory(url);
            url = pc.path.join(dir, params[j].data);
            if (!used[url]) {
              used[url] = true;
              urls.push(url);
              count++;
            }
          }
        }
      } else {
        console.warn("Update material asset loader to support new material format");
      }
    }
    if (!count) {
      callback(null, textures);
      return;
    }
    var onLoadAsset = function(err, texture) {
      textures.push(texture);
      count--;
      if (err) {
        console.error(err);
      }
      if (count === 0) {
        callback(null, textures);
      }
    };
    for (i = 0;i < urls.length;i++) {
      self.loadFromUrl(urls[i], "texture", onLoadAsset);
    }
  }, findAll:function(name, type) {
    var self = this;
    var idxs = this._names[name];
    var assets;
    if (idxs) {
      assets = idxs.map(function(idx) {
        return self._assets[idx];
      });
      if (type) {
        return assets.filter(function(asset) {
          return asset.type === type;
        });
      } else {
        return assets;
      }
    } else {
      return [];
    }
  }, _onTagAdd:function(tag, asset) {
    this._tags.add(tag, asset);
  }, _onTagRemove:function(tag, asset) {
    this._tags.remove(tag, asset);
  }, findByTag:function() {
    return this._tags.find(arguments);
  }, filter:function(callback) {
    var items = [];
    for (var i = 0, len = this._assets.length;i < len;i++) {
      if (callback(this._assets[i])) {
        items.push(this._assets[i]);
      }
    }
    return items;
  }, find:function(name, type) {
    var asset = this.findAll(name, type);
    return asset ? asset[0] : null;
  }, getAssetById:function(id) {
    console.warn("DEPRECATED: getAssetById() use get() instead");
    return this.get(id);
  }};
  return {AssetRegistry:AssetRegistry};
}());
pc.anim = {Animation:pc.Animation, Key:pc.Key, Node:pc.Node, Skeleton:pc.Skeleton};
pc.asset = {ASSET_ANIMATION:"animation", ASSET_AUDIO:"audio", ASSET_IMAGE:"image", ASSET_JSON:"json", ASSET_MODEL:"model", ASSET_MATERIAL:"material", ASSET_TEXT:"text", ASSET_TEXTURE:"texture", ASSET_CUBEMAP:"cubemap", ASSET_SCRIPT:"script"};
pc.audio = {AudioManager:pc.SoundManager, Channel:pc.Channel, Channel3d:pc.Channel3d, Listener:pc.Listener, Sound:pc.Sound};
pc.fw = {Application:pc.Application, Component:pc.Component, ComponentData:pc.ComponentData, ComponentSystem:pc.ComponentSystem, Entity:pc.Entity, FillMode:{NONE:pc.FILLMODE_NONE, FILL_WINDOW:pc.FILLMODE_FILL_WINDOW, KEEP_ASPECT:pc.FILLMODE_KEEP_ASPECT}, ResolutionMode:{AUTO:pc.RESOLUTION_AUTO, FIXED:pc.RESOLUTION_FIXED}};
pc.extend(pc.gfx, {drawQuadWithShader:pc.drawQuadWithShader, precalculatedTangents:pc.precalculatedTangents, programlib:pc.programlib, shaderChunks:pc.shaderChunks, ContextCreationError:pc.ContextCreationError, Device:pc.GraphicsDevice, IndexBuffer:pc.IndexBuffer, ProgramLibrary:pc.ProgramLibrary, RenderTarget:pc.RenderTarget, ScopeId:pc.ScopeId, Shader:pc.Shader, ShaderInput:pc.ShaderInput, Texture:pc.Texture, UnsupportedBrowserError:pc.UnsupportedBrowserError, VertexBuffer:pc.VertexBuffer, VertexFormat:pc.VertexFormat, 
VertexIterator:pc.VertexIterator});
pc.extend(pc.input, {getTouchTargetCoords:pc.getTouchTargetCoords, Controller:pc.Controller, GamePads:pc.GamePads, Keyboard:pc.Keyboard, KeyboardEvent:pc.KeyboardEvent, Mouse:pc.Mouse, MouseEvent:pc.MouseEvent, Touch:pc.Touch, TouchDevice:pc.TouchDevice, TouchEvent:pc.TouchEvent});
pc.posteffect = {createFullscreenQuad:pc.createFullscreenQuad, drawFullscreenQuad:pc.drawFullscreenQuad, PostEffect:pc.PostEffect, PostEffectQueue:pc.PostEffectQueue};
pc.extend(pc.scene, {partitionSkin:pc.partitionSkin, procedural:{calculateTangents:pc.calculateTangents, createMesh:pc.createMesh, createTorus:pc.createTorus, createCylinder:pc.createCylinder, createCapsule:pc.createCapsule, createCone:pc.createCone, createSphere:pc.createSphere, createPlane:pc.createPlane, createBox:pc.createBox}, BasicMaterial:pc.BasicMaterial, DepthMaterial:pc.DepthMaterial, ForwardRenderer:pc.ForwardRenderer, GraphNode:pc.GraphNode, Material:pc.Material, Command:pc.Command, Mesh:pc.Mesh, 
MeshInstance:pc.MeshInstance, Model:pc.Model, ParticleEmitter:pc.ParticleEmitter, PhongMaterial:pc.StandardMaterial, Picker:pc.Picker, PickMaterial:pc.PickMaterial, Projection:{ORTHOGRAPHIC:pc.PROJECTION_ORTHOGRAPHIC, PERSPECTIVE:pc.PROJECTION_PERSPECTIVE}, Scene:pc.Scene, Skin:pc.Skin, SkinInstance:pc.SkinInstance});
pc.shape = {Aabb:pc.BoundingBox, Sphere:pc.BoundingSphere, Plane:pc.Plane};
pc.time = {now:pc.now, Timer:pc.Timer};
pc.PhongMaterial = pc.StandardMaterial;
pc.extend(pc.Application.prototype, function() {
  var lineVertexFormat = null;
  var lineBatches = [];
  var quadMesh = null;
  var cubeLocalPos = null;
  var cubeWorldPos = null;
  var lineBatch = function() {
    this.numLinesAllocated = 128;
    this.vb = null;
    this.vbRam = null;
    this.mesh = null;
    this.linesUsed = 0;
    this.material = null;
    this.meshInstance = null;
  };
  lineBatch.prototype = {init:function(device, linesToAdd) {
    if (!this.mesh) {
      this.mesh = new pc.Mesh;
      this.mesh.primitive[0].type = pc.PRIMITIVE_LINES;
      this.mesh.primitive[0].base = 0;
      this.mesh.primitive[0].indexed = false;
      this.material = new pc.BasicMaterial;
      this.material.vertexColors = true;
      this.material.blend = true;
      this.material.blendType = pc.BLEND_NORMAL;
      this.material.update();
    }
    while (this.linesUsed + linesToAdd > this.numLinesAllocated) {
      this.vb = null;
      this.numLinesAllocated *= 2;
    }
    if (!this.vb) {
      this.vb = new pc.VertexBuffer(device, lineVertexFormat, this.numLinesAllocated * 2, pc.BUFFER_DYNAMIC);
      this.mesh.vertexBuffer = this.vb;
      this.vbRam = new DataView(this.vb.lock());
      if (!this.meshInstance) {
        var node = {worldTransform:pc.Mat4.IDENTITY};
        this.meshInstance = new pc.MeshInstance(node, this.mesh, this.material);
      }
    }
  }, addLines:function(position, color) {
    var multiColor = !!color.length;
    var offset = this.linesUsed * 2 * lineVertexFormat.size;
    var clr;
    for (var i = 0;i < position.length;i++) {
      this.vbRam.setFloat32(offset, position[i].x, true);
      offset += 4;
      this.vbRam.setFloat32(offset, position[i].y, true);
      offset += 4;
      this.vbRam.setFloat32(offset, position[i].z, true);
      offset += 4;
      clr = multiColor ? color[i] : color;
      this.vbRam.setUint8(offset, clr.r * 255);
      offset += 1;
      this.vbRam.setUint8(offset, clr.g * 255);
      offset += 1;
      this.vbRam.setUint8(offset, clr.b * 255);
      offset += 1;
      this.vbRam.setUint8(offset, clr.a * 255);
      offset += 1;
    }
    this.linesUsed += position.length / 2;
  }, finalize:function(drawCalls) {
    if (this.linesUsed > 0) {
      this.vb.setData(this.vbRam.buffer);
      this.mesh.primitive[0].count = this.linesUsed * 2;
      drawCalls.push(this.meshInstance);
      this.linesUsed = 0;
    }
  }};
  function _addLines(batchId, position, color) {
    if (!lineVertexFormat) {
      lineVertexFormat = new pc.VertexFormat(this.graphicsDevice, [{semantic:pc.SEMANTIC_POSITION, components:3, type:pc.ELEMENTTYPE_FLOAT32}, {semantic:pc.SEMANTIC_COLOR, components:4, type:pc.ELEMENTTYPE_UINT8, normalize:true}]);
      this.on("prerender", this._preRenderImmediate, this);
    }
    if (!lineBatches[batchId]) {
      lineBatches[batchId] = new lineBatch;
      lineBatches[batchId].init(this.graphicsDevice, position.length / 2);
      if (batchId === pc.LINEBATCH_OVERLAY) {
        lineBatches[batchId].material.depthTest = false;
        lineBatches[batchId].meshInstance.layer = pc.LAYER_GIZMO;
      } else {
        if (batchId === pc.LINEBATCH_GIZMO) {
          lineBatches[batchId].meshInstance.layer = pc.LAYER_GIZMO;
        }
      }
    } else {
      lineBatches[batchId].init(this.graphicsDevice, position.length / 2);
    }
    lineBatches[batchId].addLines(position, color);
  }
  function renderLine(start, end, color, arg3, arg4) {
    var endColor = color;
    var lineType = pc.LINEBATCH_WORLD;
    if (arg3) {
      if (typeof arg3 === "number") {
        lineType = arg3;
      } else {
        endColor = arg3;
        if (arg4) {
          lineType = arg4;
        }
      }
    }
    this._addLines(lineType, [start, end], [color, endColor]);
  }
  function renderLines(position, color, lineType) {
    if (lineType === undefined) {
      lineType = pc.LINEBATCH_WORLD;
    }
    var multiColor = !!color.length;
    if (multiColor) {
      if (position.length !== color.length) {
        pc.log.error("renderLines: position/color arrays have different lengths");
        return;
      }
    }
    if (position.length % 2 !== 0) {
      pc.log.error("renderLines: array length is not divisible by 2");
      return;
    }
    this._addLines(lineType, position, color);
  }
  function renderWireCube(matrix, color, lineType) {
    if (lineType === undefined) {
      lineType = pc.LINEBATCH_WORLD;
    }
    var i;
    if (!cubeLocalPos) {
      var x = .5;
      cubeLocalPos = [new pc.Vec3(-x, -x, -x), new pc.Vec3(-x, x, -x), new pc.Vec3(x, x, -x), new pc.Vec3(x, -x, -x), new pc.Vec3(-x, -x, x), new pc.Vec3(-x, x, x), new pc.Vec3(x, x, x), new pc.Vec3(x, -x, x)];
      cubeWorldPos = [new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3, new pc.Vec3];
    }
    for (i = 0;i < 8;i++) {
      matrix.transformPoint(cubeLocalPos[i], cubeWorldPos[i]);
    }
    this.renderLines([cubeWorldPos[0], cubeWorldPos[1], cubeWorldPos[1], cubeWorldPos[2], cubeWorldPos[2], cubeWorldPos[3], cubeWorldPos[3], cubeWorldPos[0], cubeWorldPos[4], cubeWorldPos[5], cubeWorldPos[5], cubeWorldPos[6], cubeWorldPos[6], cubeWorldPos[7], cubeWorldPos[7], cubeWorldPos[4], cubeWorldPos[0], cubeWorldPos[4], cubeWorldPos[1], cubeWorldPos[5], cubeWorldPos[2], cubeWorldPos[6], cubeWorldPos[3], cubeWorldPos[7]], color, lineType);
  }
  function _preRenderImmediate() {
    for (var i = 0;i < 3;i++) {
      if (lineBatches[i]) {
        lineBatches[i].finalize(this.scene.immediateDrawCalls);
      }
    }
  }
  function renderMeshInstance(meshInstance) {
    this.scene.immediateDrawCalls.push(meshInstance);
  }
  function renderMesh(mesh, material, matrix) {
    var node = {worldTransform:matrix};
    var instance = new pc.MeshInstance(node, mesh, material);
    this.scene.immediateDrawCalls.push(instance);
  }
  function renderQuad(matrix, material, layer) {
    if (!quadMesh) {
      var format = new pc.VertexFormat(this.graphicsDevice, [{semantic:pc.SEMANTIC_POSITION, components:3, type:pc.ELEMENTTYPE_FLOAT32}]);
      var quadVb = new pc.VertexBuffer(this.graphicsDevice, format, 4);
      var iterator = new pc.VertexIterator(quadVb);
      iterator.element[pc.SEMANTIC_POSITION].set(-.5, -.5, 0);
      iterator.next();
      iterator.element[pc.SEMANTIC_POSITION].set(.5, -.5, 0);
      iterator.next();
      iterator.element[pc.SEMANTIC_POSITION].set(-.5, .5, 0);
      iterator.next();
      iterator.element[pc.SEMANTIC_POSITION].set(.5, .5, 0);
      iterator.end();
      quadMesh = new pc.Mesh;
      quadMesh.vertexBuffer = quadVb;
      quadMesh.primitive[0].type = pc.PRIMITIVE_TRISTRIP;
      quadMesh.primitive[0].base = 0;
      quadMesh.primitive[0].count = 4;
      quadMesh.primitive[0].indexed = false;
    }
    var node = {worldTransform:matrix};
    var quad = new pc.MeshInstance(node, quadMesh, material);
    if (layer) {
      quad.layer = layer;
    }
    this.scene.immediateDrawCalls.push(quad);
  }
  return {renderMeshInstance:renderMeshInstance, renderMesh:renderMesh, renderLine:renderLine, renderLines:renderLines, renderQuad:renderQuad, renderWireCube:renderWireCube, _addLines:_addLines, _preRenderImmediate:_preRenderImmediate};
}());
pc.extend(pc, function() {
  var maxSize = 2048;
  var maskDynamic = 1;
  var maskBaked = 2;
  var maskLightmap = 4;
  var sceneLightmaps = [];
  var sceneLightmapsNode = [];
  var lmCamera;
  var tempVec = new pc.Vec3;
  var bounds = new pc.BoundingBox;
  var lightBounds = new pc.BoundingBox;
  var tempSphere = {};
  var PASS_COLOR = 0;
  var PASS_DIR = 1;
  var passTexName = ["texture_lightMap", "texture_dirLightMap"];
  var passMaterial = [];
  function collectModels(node, nodes, nodesMeshInstances, allNodes) {
    if (!node.enabled) {
      return;
    }
    var i;
    if (node.model && node.model.model && node.model.enabled) {
      if (allNodes) {
        allNodes.push(node);
      }
      if (node.model.data.lightmapped) {
        if (nodes) {
          var hasUv1 = true;
          var meshInstances = node.model.model.meshInstances;
          for (i = 0;i < meshInstances.length;i++) {
            if (!meshInstances[i].mesh.vertexBuffer.format.hasUv1) {
              hasUv1 = false;
              break;
            }
          }
          if (hasUv1) {
            var j;
            var isInstance;
            var notInstancedMeshInstances = [];
            for (i = 0;i < meshInstances.length;i++) {
              isInstance = false;
              for (j = 0;j < meshInstances.length;j++) {
                if (i !== j) {
                  if (meshInstances[i].mesh === meshInstances[j].mesh) {
                    isInstance = true;
                  }
                }
              }
              if (isInstance) {
                nodes.push(node);
                nodesMeshInstances.push([meshInstances[i]]);
              } else {
                notInstancedMeshInstances.push(meshInstances[i]);
              }
            }
            if (notInstancedMeshInstances.length > 0) {
              nodes.push(node);
              nodesMeshInstances.push(notInstancedMeshInstances);
            }
          }
        }
      }
    }
    for (i = 0;i < node._children.length;i++) {
      collectModels(node._children[i], nodes, nodesMeshInstances, allNodes);
    }
  }
  var Lightmapper = function(device, root, scene, renderer, assets) {
    this.device = device;
    this.root = root;
    this.scene = scene;
    this.renderer = renderer;
    this.assets = assets;
    this._stats = {renderPasses:0, lightmapCount:0, totalRenderTime:0, forwardTime:0, fboTime:0, shadowMapTime:0, compileTime:0, shadersLinked:0};
  };
  Lightmapper.prototype = {calculateLightmapSize:function(node) {
    var sizeMult = this.scene.lightmapSizeMultiplier || 16;
    var scale = tempVec;
    var parent;
    var area = {x:1, y:1, z:1, uv:1};
    if (node.model.asset) {
      var data = this.assets.get(node.model.asset).data;
      if (data.area) {
        area.x = data.area.x;
        area.y = data.area.y;
        area.z = data.area.z;
        area.uv = data.area.uv;
      }
    } else {
      if (node.model._area) {
        var data = node.model;
        if (data._area) {
          area.x = data._area.x;
          area.y = data._area.y;
          area.z = data._area.z;
          area.uv = data._area.uv;
        }
      }
    }
    var areaMult = node.model.lightmapSizeMultiplier || 1;
    area.x *= areaMult;
    area.y *= areaMult;
    area.z *= areaMult;
    scale.copy(node.localScale);
    parent = node._parent;
    while (parent) {
      scale.mul(parent.localScale);
      parent = parent._parent;
    }
    var totalArea = area.x * scale.y * scale.z + area.y * scale.x * scale.z + area.z * scale.x * scale.y;
    totalArea /= area.uv;
    totalArea = Math.sqrt(totalArea);
    return Math.min(pc.math.nextPowerOfTwo(totalArea * sizeMult), this.scene.lightmapMaxResolution || maxSize);
  }, bake:function(nodes, mode) {
    var i, j;
    var device = this.device;
    var scene = this.scene;
    var stats = this._stats;
    var passCount = 1;
    if (mode === undefined) {
      mode = pc.BAKE_COLORDIR;
    }
    if (mode === pc.BAKE_COLORDIR) {
      passCount = 2;
    }
    var pass;
    var allNodes = [];
    var nodesMeshInstances = [];
    if (!nodes) {
      for (i = 0;i < sceneLightmaps.length;i++) {
        for (j = 0;j < sceneLightmaps[i].length;j++) {
          sceneLightmaps[i][j].destroy();
        }
      }
      sceneLightmaps = [];
      sceneLightmapsNode = [];
      nodes = [];
      collectModels(this.root, nodes, nodesMeshInstances, allNodes);
    } else {
      var k;
      for (i = 0;i < sceneLightmaps.length;i++) {
        for (i = j;j < nodes.length;j++) {
          if (sceneLightmapsNode[i] === nodes[j]) {
            for (k = 0;k < sceneLightmaps[i].length;k++) {
              sceneLightmaps[i][k].destroy();
            }
          }
        }
      }
      sceneLightmaps = [];
      sceneLightmapsNode = [];
      var _nodes = [];
      for (i = 0;i < nodes.length;i++) {
        collectModels(nodes[i], _nodes, nodesMeshInstances);
      }
      nodes = _nodes;
      collectModels(this.root, null, null, allNodes);
    }
    if (nodes.length === 0) {
      device.fire("lightmapper:end", {timestamp:pc.now(), target:this});
      return;
    }
    var revertStatic = false;
    if (scene._needsStaticPrepare) {
      scene._needsStaticPrepare = false;
      revertStatic = true;
    }
    var texSize = [];
    var lmaps = [[], []];
    var texPool = {};
    var size;
    var tex;
    var instances;
    var blackTex = new pc.Texture(this._device, {width:4, height:4, format:pc.PIXELFORMAT_R8_G8_B8_A8});
    for (i = 0;i < nodes.length;i++) {
      size = this.calculateLightmapSize(nodes[i]);
      texSize.push(size);
      for (pass = 0;pass < passCount;pass++) {
        tex = new pc.Texture(device, {width:size, height:size, format:pc.PIXELFORMAT_R8_G8_B8_A8, mipmaps:false, rgbm:pass === PASS_COLOR, minFilter:pc.FILTER_NEAREST, magFilter:pc.FILTER_NEAREST});
        lmaps[pass].push(tex);
      }
      if (!texPool[size]) {
        var tex2 = new pc.Texture(device, {width:size, height:size, format:pc.PIXELFORMAT_R8_G8_B8_A8, mipmaps:false, rgbm:true, minFilter:pc.FILTER_NEAREST, magFilter:pc.FILTER_NEAREST});
        var targ2 = new pc.RenderTarget(device, tex2, {depth:false});
        texPool[size] = targ2;
      }
    }
    var lights = [];
    var origMask = [];
    var origShadowMode = [];
    var origEnabled = [];
    var sceneLights = scene._lights;
    var mask;
    for (i = 0;i < sceneLights.length;i++) {
      if (sceneLights[i]._enabled) {
        mask = sceneLights[i]._mask;
        if ((mask & maskLightmap) !== 0) {
          origMask.push(mask);
          origShadowMode.push(sceneLights[i].shadowUpdateMode);
          sceneLights[i]._mask = 4294967295;
          sceneLights[i].shadowUpdateMode = sceneLights[i]._type === pc.LIGHTTYPE_DIRECTIONAL ? pc.SHADOWUPDATE_REALTIME : pc.SHADOWUPDATE_THISFRAME;
          lights.push(sceneLights[i]);
          sceneLights[i].isStatic = false;
        }
      }
      origEnabled.push(sceneLights[i]._enabled);
      sceneLights[i].enabled = false;
    }
    var chunks = pc.shaderChunks;
    var xformUv1 = chunks.transformUv1VS;
    var bakeLmEnd = chunks.bakeLmEndPS;
    var dilate = chunks.dilatePS;
    var dilateShader = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, dilate, "lmDilate");
    var constantTexSource = device.scope.resolve("source");
    var constantPixelOffset = device.scope.resolve("pixelOffset");
    var constantBakeDir = device.scope.resolve("bakeDir");
    var pixelOffset = new pc.Vec2;
    var lms = {};
    var drawCalls = scene.drawCalls;
    for (i = 0;i < drawCalls.length;i++) {
      if (drawCalls[i].node) {
        drawCalls[i].node.getWorldTransform();
      }
    }
    var origFog = scene.fog;
    var origAmbientR = scene.ambientLight.data[0];
    var origAmbientG = scene.ambientLight.data[1];
    var origAmbientB = scene.ambientLight.data[2];
    var origDrawCalls = scene.drawCalls;
    scene.fog = pc.FOG_NONE;
    scene.ambientLight.data[0] = 0;
    scene.ambientLight.data[1] = 0;
    scene.ambientLight.data[2] = 0;
    if (!lmCamera) {
      lmCamera = new pc.Camera;
      lmCamera._node = new pc.GraphNode;
      lmCamera.clearColor[0] = 0;
      lmCamera.clearColor[1] = 0;
      lmCamera.clearColor[2] = 0;
      lmCamera.clearColor[3] = 0;
      lmCamera.clearDepth = 1;
      lmCamera.clearFlags = pc.CLEARFLAG_COLOR;
      lmCamera.clearStencil = null;
      lmCamera.frustumCulling = false;
    }
    var node;
    var lm, rcv, mat, m;
    for (node = 0;node < allNodes.length;node++) {
      rcv = allNodes[node].model.model.meshInstances;
      for (i = 0;i < rcv.length;i++) {
        rcv[i]._shaderDefs &= ~(pc.SHADERDEF_LM | pc.SHADERDEF_DIRLM);
      }
    }
    var origCastShadows = [];
    var origCasters2 = scene.shadowCasters.slice();
    for (node = 0;node < allNodes.length;node++) {
      origCastShadows[node] = allNodes[node].model.castShadows;
      allNodes[node].model.castShadows = allNodes[node].model.data.castShadowsLightmap;
    }
    var origCasters = scene.shadowCasters;
    var casters = [];
    var instanceClone, prop;
    for (i = 0;i < origCasters.length;i++) {
      m = origCasters[i];
      instanceClone = new pc.MeshInstance(m.node, m.mesh, m.material);
      for (prop in m) {
        if (m.hasOwnProperty(prop)) {
          instanceClone[prop] = m[prop];
        }
      }
      casters.push(instanceClone);
    }
    scene.shadowCasters = casters;
    var origMat = [];
    var nodeBounds = [];
    var nodeTarg = [[], []];
    var targ, targTmp, texTmp;
    var light, shadowCam;
    var nodeLightCount = [];
    nodeLightCount.length = nodes.length;
    scene.updateShadersFunc(device);
    for (node = 0;node < nodes.length;node++) {
      rcv = nodesMeshInstances[node];
      for (i = 0;i < rcv.length;i++) {
        mat = rcv[i].material;
        origMat.push(mat);
      }
    }
    var lmMaterial;
    for (pass = 0;pass < passCount;pass++) {
      if (!passMaterial[pass]) {
        lmMaterial = new pc.StandardMaterial;
        lmMaterial.chunks.transformVS = xformUv1;
        if (pass === PASS_COLOR) {
          lmMaterial.chunks.endPS = bakeLmEnd;
          lmMaterial.ambient = new pc.Color(0, 0, 0);
          lmMaterial.ambientTint = true;
        } else {
          lmMaterial.chunks.basePS = chunks.basePS + "\nuniform sampler2D texture_dirLightMap;\nuniform float bakeDir;\n";
          lmMaterial.chunks.endPS = chunks.bakeDirLmEndPS;
        }
        lmMaterial.chunks.outputAlphaPS = "\n";
        lmMaterial.chunks.outputAlphaOpaquePS = "\n";
        lmMaterial.chunks.outputAlphaPremulPS = "\n";
        lmMaterial.cull = pc.CULLFACE_NONE;
        lmMaterial.forceUv1 = true;
        lmMaterial.update();
        lmMaterial.updateShader(device, scene);
        passMaterial[pass] = lmMaterial;
      }
    }
    for (node = 0;node < nodes.length;node++) {
      rcv = nodesMeshInstances[node];
      nodeLightCount[node] = 0;
      if (rcv.length > 0) {
        bounds.copy(rcv[0].aabb);
        for (i = 0;i < rcv.length;i++) {
          rcv[i].node.getWorldTransform();
          bounds.add(rcv[i].aabb);
        }
      }
      var nbounds = new pc.BoundingBox;
      nbounds.copy(bounds);
      nodeBounds.push(nbounds);
      for (i = 0;i < rcv.length;i++) {
        m = rcv[i];
        m._shaderDefs &= ~(pc.SHADERDEF_LM | pc.SHADERDEF_DIRLM);
        m.mask = maskLightmap;
        m.deleteParameter("texture_lightMap");
        m.deleteParameter("texture_dirLightMap");
        m.setParameter("texture_dirLightMap", blackTex);
      }
      for (pass = 0;pass < passCount;pass++) {
        lm = lmaps[pass][node];
        targ = new pc.RenderTarget(device, lm, {depth:false});
        nodeTarg[pass].push(targ);
      }
    }
    for (j = 0;j < lights.length;j++) {
      lights[j].enabled = false;
    }
    var shadersUpdatedOn1stPass = false;
    for (i = 0;i < lights.length;i++) {
      lights[i].enabled = true;
      lights[i]._cacheShadowMap = true;
      if (lights[i]._type !== pc.LIGHTTYPE_DIRECTIONAL) {
        lights[i]._node.getWorldTransform();
        lights[i].getBoundingSphere(tempSphere);
        lightBounds.center = tempSphere.center;
        lightBounds.halfExtents.x = tempSphere.radius;
        lightBounds.halfExtents.y = tempSphere.radius;
        lightBounds.halfExtents.z = tempSphere.radius;
      }
      if (lights[i]._type === pc.LIGHTTYPE_SPOT) {
        light = lights[i];
        shadowCam = this.renderer.getShadowCamera(device, light);
        shadowCam._node.setPosition(light._node.getPosition());
        shadowCam._node.setRotation(light._node.getRotation());
        shadowCam._node.rotateLocal(-90, 0, 0);
        shadowCam.projection = pc.PROJECTION_PERSPECTIVE;
        shadowCam.nearClip = light.attenuationEnd / 1E3;
        shadowCam.farClip = light.attenuationEnd;
        shadowCam.aspectRatio = 1;
        shadowCam.fov = light._outerConeAngle * 2;
        this.renderer.updateCameraFrustum(shadowCam);
      }
      for (node = 0;node < nodes.length;node++) {
        rcv = nodesMeshInstances[node];
        bounds = nodeBounds[node];
        scene.drawCalls = [];
        for (j = 0;j < rcv.length;j++) {
          scene.drawCalls.push(rcv[j]);
        }
        scene.updateShaders = true;
        if (lights[i]._type === pc.LIGHTTYPE_DIRECTIONAL) {
          tempVec.copy(bounds.center);
          tempVec.y += bounds.halfExtents.y;
          lmCamera._node.setPosition(tempVec);
          lmCamera._node.setEulerAngles(-90, 0, 0);
          var frustumSize = Math.max(bounds.halfExtents.x, bounds.halfExtents.z);
          lmCamera.projection = pc.PROJECTION_ORTHOGRAPHIC;
          lmCamera.nearClip = 0;
          lmCamera.farClip = bounds.halfExtents.y * 2;
          lmCamera.aspectRatio = 1;
          lmCamera.orthoHeight = frustumSize;
        } else {
          if (!lightBounds.intersects(bounds)) {
            continue;
          }
        }
        if (lights[i]._type === pc.LIGHTTYPE_SPOT) {
          var nodeVisible = false;
          for (j = 0;j < rcv.length;j++) {
            if (this.renderer._isVisible(shadowCam, rcv[j])) {
              nodeVisible = true;
              break;
            }
          }
          if (!nodeVisible) {
            continue;
          }
        }
        for (pass = 0;pass < passCount;pass++) {
          lm = lmaps[pass][node];
          targ = nodeTarg[pass][node];
          targTmp = texPool[lm.width];
          texTmp = targTmp.colorBuffer;
          if (pass === 0) {
            shadersUpdatedOn1stPass = scene.updateShaders;
          } else {
            if (shadersUpdatedOn1stPass) {
              scene.updateShaders = true;
            }
          }
          for (j = 0;j < rcv.length;j++) {
            rcv[j].material = passMaterial[pass];
          }
          lmCamera.renderTarget = targTmp;
          if (pass === PASS_DIR) {
            constantBakeDir.setValue(lights[i].bakeDir ? 1 : 0);
          }
          this.renderer.render(scene, lmCamera);
          stats.shadowMapTime += this.renderer._shadowMapTime;
          stats.forwardTime += this.renderer._forwardTime;
          stats.renderPasses++;
          lmaps[pass][node] = texTmp;
          nodeTarg[pass][node] = targTmp;
          texPool[lm.width] = targ;
          for (j = 0;j < rcv.length;j++) {
            m = rcv[j];
            m.setParameter(passTexName[pass], texTmp);
            m._shaderDefs |= pc.SHADERDEF_LM;
          }
        }
        nodeLightCount[node]++;
      }
      lights[i].enabled = false;
      lights[i]._cacheShadowMap = false;
    }
    var id = 0;
    var sceneLmaps;
    for (node = 0;node < nodes.length;node++) {
      rcv = nodesMeshInstances[node];
      sceneLmaps = [];
      for (pass = 0;pass < passCount;pass++) {
        lm = lmaps[pass][node];
        targ = nodeTarg[pass][node];
        targTmp = texPool[lm.width];
        texTmp = targTmp.colorBuffer;
        var numDilates2x = 4;
        pixelOffset.set(1 / lm.width, 1 / lm.height);
        constantPixelOffset.setValue(pixelOffset.data);
        for (i = 0;i < numDilates2x;i++) {
          constantTexSource.setValue(lm);
          pc.drawQuadWithShader(device, targTmp, dilateShader);
          constantTexSource.setValue(texTmp);
          pc.drawQuadWithShader(device, targ, dilateShader);
        }
        for (i = 0;i < rcv.length;i++) {
          m = rcv[i];
          if (pass === 0) {
            m.mask = maskBaked;
            rcv[i].material = origMat[id];
            id++;
          }
          rcv[i].setParameter(passTexName[pass], lm);
          if (pass === PASS_DIR) {
            rcv[i]._shaderDefs |= pc.SHADERDEF_DIRLM;
          }
        }
        sceneLmaps[pass] = lm;
        if (pass === passCount - 1) {
          targ.destroy();
        }
      }
      sceneLightmaps.push(sceneLmaps);
      sceneLightmapsNode.push(nodes[node]);
    }
    for (var key in texPool) {
      if (texPool.hasOwnProperty(key)) {
        texPool[key].colorBuffer.destroy();
        texPool[key].destroy();
      }
    }
    for (i = 0;i < sceneLightmaps.length;i++) {
      for (j = 0;j < sceneLightmaps[i].length;j++) {
        tex = sceneLightmaps[i][j];
        tex.minFilter = pc.FILTER_LINEAR;
        tex.magFilter = pc.FILTER_LINEAR;
      }
    }
    for (node = 0;node < allNodes.length;node++) {
      allNodes[node].model.castShadows = origCastShadows[node];
    }
    scene.shadowCasters = origCasters2;
    for (i = 0;i < lights.length;i++) {
      lights[i]._mask = origMask[i];
      lights[i].shadowUpdateMode = origShadowMode[i];
    }
    for (i = 0;i < sceneLights.length;i++) {
      sceneLights[i].enabled = origEnabled[i];
    }
    scene.drawCalls = origDrawCalls;
    scene.fog = origFog;
    scene.ambientLight.data[0] = origAmbientR;
    scene.ambientLight.data[1] = origAmbientG;
    scene.ambientLight.data[2] = origAmbientB;
    if (revertStatic) {
      scene._needsStaticPrepare = true;
    }
  }};
  return {Lightmapper:Lightmapper, MASK_DYNAMIC:maskDynamic, MASK_BAKED:maskBaked, MASK_LIGHTMAP:maskLightmap};
}());

