text: (function () {
      'use strict';
  
      /**
       * Calculates the length of a vec3
       *
       * @param {vec3} a vector to calculate length of
       * @returns {Number} length of a
       */
  
      function length(a) {
        let x = a[0];
        let y = a[1];
        let z = a[2];
        return Math.sqrt(x * x + y * y + z * z);
      }
      /**
       * Copy the values from one vec3 to another
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the source vector
       * @returns {vec3} out
       */
  
      function copy(out, a) {
        out[0] = a[0];
        out[1] = a[1];
        out[2] = a[2];
        return out;
      }
      /**
       * Set the components of a vec3 to the given values
       *
       * @param {vec3} out the receiving vector
       * @param {Number} x X component
       * @param {Number} y Y component
       * @param {Number} z Z component
       * @returns {vec3} out
       */
  
      function set(out, x, y, z) {
        out[0] = x;
        out[1] = y;
        out[2] = z;
        return out;
      }
      /**
       * Adds two vec3's
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @returns {vec3} out
       */
  
      function add(out, a, b) {
        out[0] = a[0] + b[0];
        out[1] = a[1] + b[1];
        out[2] = a[2] + b[2];
        return out;
      }
      /**
       * Subtracts vector b from vector a
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @returns {vec3} out
       */
  
      function subtract(out, a, b) {
        out[0] = a[0] - b[0];
        out[1] = a[1] - b[1];
        out[2] = a[2] - b[2];
        return out;
      }
      /**
       * Multiplies two vec3's
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @returns {vec3} out
       */
  
      function multiply(out, a, b) {
        out[0] = a[0] * b[0];
        out[1] = a[1] * b[1];
        out[2] = a[2] * b[2];
        return out;
      }
      /**
       * Divides two vec3's
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @returns {vec3} out
       */
  
      function divide(out, a, b) {
        out[0] = a[0] / b[0];
        out[1] = a[1] / b[1];
        out[2] = a[2] / b[2];
        return out;
      }
      /**
       * Scales a vec3 by a scalar number
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the vector to scale
       * @param {Number} b amount to scale the vector by
       * @returns {vec3} out
       */
  
      function scale(out, a, b) {
        out[0] = a[0] * b;
        out[1] = a[1] * b;
        out[2] = a[2] * b;
        return out;
      }
      /**
       * Calculates the euclidian distance between two vec3's
       *
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @returns {Number} distance between a and b
       */
  
      function distance(a, b) {
        let x = b[0] - a[0];
        let y = b[1] - a[1];
        let z = b[2] - a[2];
        return Math.sqrt(x * x + y * y + z * z);
      }
      /**
       * Calculates the squared euclidian distance between two vec3's
       *
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @returns {Number} squared distance between a and b
       */
  
      function squaredDistance(a, b) {
        let x = b[0] - a[0];
        let y = b[1] - a[1];
        let z = b[2] - a[2];
        return x * x + y * y + z * z;
      }
      /**
       * Calculates the squared length of a vec3
       *
       * @param {vec3} a vector to calculate squared length of
       * @returns {Number} squared length of a
       */
  
      function squaredLength(a) {
        let x = a[0];
        let y = a[1];
        let z = a[2];
        return x * x + y * y + z * z;
      }
      /**
       * Negates the components of a vec3
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a vector to negate
       * @returns {vec3} out
       */
  
      function negate(out, a) {
        out[0] = -a[0];
        out[1] = -a[1];
        out[2] = -a[2];
        return out;
      }
      /**
       * Returns the inverse of the components of a vec3
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a vector to invert
       * @returns {vec3} out
       */
  
      function inverse(out, a) {
        out[0] = 1.0 / a[0];
        out[1] = 1.0 / a[1];
        out[2] = 1.0 / a[2];
        return out;
      }
      /**
       * Normalize a vec3
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a vector to normalize
       * @returns {vec3} out
       */
  
      function normalize(out, a) {
        let x = a[0];
        let y = a[1];
        let z = a[2];
        let len = x * x + y * y + z * z;
  
        if (len > 0) {
          //TODO: evaluate use of glm_invsqrt here?
          len = 1 / Math.sqrt(len);
        }
  
        out[0] = a[0] * len;
        out[1] = a[1] * len;
        out[2] = a[2] * len;
        return out;
      }
      /**
       * Calculates the dot product of two vec3's
       *
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @returns {Number} dot product of a and b
       */
  
      function dot(a, b) {
        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
      }
      /**
       * Computes the cross product of two vec3's
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @returns {vec3} out
       */
  
      function cross(out, a, b) {
        let ax = a[0],
            ay = a[1],
            az = a[2];
        let bx = b[0],
            by = b[1],
            bz = b[2];
        out[0] = ay * bz - az * by;
        out[1] = az * bx - ax * bz;
        out[2] = ax * by - ay * bx;
        return out;
      }
      /**
       * Performs a linear interpolation between two vec3's
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the first operand
       * @param {vec3} b the second operand
       * @param {Number} t interpolation amount between the two inputs
       * @returns {vec3} out
       */
  
      function lerp(out, a, b, t) {
        let ax = a[0];
        let ay = a[1];
        let az = a[2];
        out[0] = ax + t * (b[0] - ax);
        out[1] = ay + t * (b[1] - ay);
        out[2] = az + t * (b[2] - az);
        return out;
      }
      /**
       * Transforms the vec3 with a mat4.
       * 4th vector component is implicitly '1'
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the vector to transform
       * @param {mat4} m matrix to transform with
       * @returns {vec3} out
       */
  
      function transformMat4(out, a, m) {
        let x = a[0],
            y = a[1],
            z = a[2];
        let w = m[3] * x + m[7] * y + m[11] * z + m[15];
        w = w || 1.0;
        out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
        out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
        out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
        return out;
      }
      /**
       * Same as above but doesn't apply translation.
       * Useful for rays.
       */
  
      function scaleRotateMat4(out, a, m) {
        let x = a[0],
            y = a[1],
            z = a[2];
        let w = m[3] * x + m[7] * y + m[11] * z + m[15];
        w = w || 1.0;
        out[0] = (m[0] * x + m[4] * y + m[8] * z) / w;
        out[1] = (m[1] * x + m[5] * y + m[9] * z) / w;
        out[2] = (m[2] * x + m[6] * y + m[10] * z) / w;
        return out;
      }
      /**
       * Transforms the vec3 with a quat
       *
       * @param {vec3} out the receiving vector
       * @param {vec3} a the vector to transform
       * @param {quat} q quaternion to transform with
       * @returns {vec3} out
       */
  
      function transformQuat(out, a, q) {
        // benchmarks: https://jsperf.com/quaternion-transform-vec3-implementations-fixed
        let x = a[0],
            y = a[1],
            z = a[2];
        let qx = q[0],
            qy = q[1],
            qz = q[2],
            qw = q[3];
        let uvx = qy * z - qz * y;
        let uvy = qz * x - qx * z;
        let uvz = qx * y - qy * x;
        let uuvx = qy * uvz - qz * uvy;
        let uuvy = qz * uvx - qx * uvz;
        let uuvz = qx * uvy - qy * uvx;
        let w2 = qw * 2;
        uvx *= w2;
        uvy *= w2;
        uvz *= w2;
        uuvx *= 2;
        uuvy *= 2;
        uuvz *= 2;
        out[0] = x + uvx + uuvx;
        out[1] = y + uvy + uuvy;
        out[2] = z + uvz + uuvz;
        return out;
      }
      /**
       * Get the angle between two 3D vectors
       * @param {vec3} a The first operand
       * @param {vec3} b The second operand
       * @returns {Number} The angle in radians
       */
  
      const angle = function () {
        const tempA = [0, 0, 0];
        const tempB = [0, 0, 0];
        return function (a, b) {
          copy(tempA, a);
          copy(tempB, b);
          normalize(tempA, tempA);
          normalize(tempB, tempB);
          let cosine = dot(tempA, tempB);
  
          if (cosine > 1.0) {
            return 0;
          } else if (cosine < -1.0) {
            return Math.PI;
          } else {
            return Math.acos(cosine);
          }
        };
      }();
      /**
       * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
       *
       * @param {vec3} a The first vector.
       * @param {vec3} b The second vector.
       * @returns {Boolean} True if the vectors are equal, false otherwise.
       */
  
      function exactEquals(a, b) {
        return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
      }
  
      const isArrayLike = term => {
        if (term.length) return true;
        return false;
      };
      const isMesh = node => {
        return !!node.draw;
      };
  
      class Vec3 extends Array {
        // TODO: only be used in Camera class
        constructor(x = 0, y = x, z = x) {
          super(x, y, z);
          this.constant = void 0;
          return this;
        }
  
        get x() {
          return this[0];
        }
  
        get y() {
          return this[1];
        }
  
        get z() {
          return this[2];
        }
  
        set x(v) {
          this[0] = v;
        }
  
        set y(v) {
          this[1] = v;
        }
  
        set z(v) {
          this[2] = v;
        }
  
        set(x, y = x, z = x) {
          if (isArrayLike(x)) return this.copy(x);
          set(this, x, y, z);
          return this;
        }
  
        copy(v) {
          copy(this, v);
          return this;
        }
  
        add(va, vb) {
          if (vb) add(this, va, vb);else add(this, this, va);
          return this;
        }
  
        sub(va, vb) {
          if (vb) subtract(this, va, vb);else subtract(this, this, va);
          return this;
        }
  
        multiply(v) {
          if (v.length) multiply(this, this, v);else scale(this, this, v);
          return this;
        }
  
        divide(v) {
          if (v.length) divide(this, this, v);else scale(this, this, 1 / v);
          return this;
        }
  
        inverse(v = this) {
          inverse(this, v);
          return this;
        } // Can't use 'length' as Array.prototype uses it
  
  
        len() {
          return length(this);
        }
  
        distance(v) {
          if (v) return distance(this, v);else return length(this);
        }
  
        squaredLen() {
          return squaredLength(this);
        }
  
        squaredDistance(v) {
          if (v) return squaredDistance(this, v);else return squaredLength(this);
        }
  
        negate(v = this) {
          negate(this, v);
          return this;
        }
  
        cross(va, vb) {
          if (vb) cross(this, va, vb);else cross(this, this, va);
          return this;
        }
  
        scale(v) {
          scale(this, this, v);
          return this;
        }
  
        normalize() {
          normalize(this, this);
          return this;
        }
  
        dot(v) {
          return dot(this, v);
        }
  
        equals(v) {
          return exactEquals(this, v);
        }
  
        applyMatrix4(mat4) {
          transformMat4(this, this, mat4);
          return this;
        }
  
        scaleRotateMatrix4(mat4) {
          scaleRotateMat4(this, this, mat4);
          return this;
        }
  
        applyQuaternion(q) {
          transformQuat(this, this, q);
          return this;
        }
  
        angle(v) {
          return angle(this, v);
        }
  
        lerp(v, t) {
          lerp(this, this, v, t);
          return this;
        }
  
        clone() {
          return new Vec3(this[0], this[1], this[2]);
        }
  
        fromArray(a, o = 0) {
          this[0] = a[o];
          this[1] = a[o + 1];
          this[2] = a[o + 2];
          return this;
        }
  
        toArray(a = [], o = 0) {
          a[o] = this[0];
          a[o + 1] = this[1];
          a[o + 2] = this[2];
          return a;
        }
  
        transformDirection(mat4) {
          const x = this[0];
          const y = this[1];
          const z = this[2];
          this[0] = mat4[0] * x + mat4[4] * y + mat4[8] * z;
          this[1] = mat4[1] * x + mat4[5] * y + mat4[9] * z;
          this[2] = mat4[2] * x + mat4[6] * y + mat4[10] * z;
          return this.normalize();
        }
  
      }
  
      // attribute params
      const tempVec3 = new Vec3();
      let ID = 1;
      let ATTR_ID = 1; // export interface Attributes {
      //     position: { size: number, data: UInt16Array; },
      //     normal: { size: number, data: normal; },
      //     uv: { size: number, data: uv; },
      //     index: { data: index; },
      // }
  
      // To stop inifinite warnings
      let isBoundsWarned = false;
      class Geometry {
        constructor(gl, attributes = {}) {
          this.gl = void 0;
          this.id = void 0;
          this.attributes = void 0;
          this.VAOs = void 0;
          this.drawRange = void 0;
          this.instancedCount = void 0;
          this.glState = void 0;
          this.isInstanced = void 0;
          this.bounds = void 0;
          this.raycast = 'box';
          if (!gl.canvas) console.error('gl not passed as fist argument to Geometry');
          this.gl = gl;
          this.attributes = attributes;
          this.id = ID++; // Store one VAO per program attribute locations order
  
          this.VAOs = {};
          this.drawRange = {
            start: 0,
            count: 0
          };
          this.instancedCount = 0; // Unbind current VAO so that new buffers don't get added to active mesh
  
          this.gl.renderer.bindVertexArray(null);
          this.gl.renderer.currentGeometry = null; // Alias for state store to avoid redundant calls for global state
  
          this.glState = this.gl.renderer.state; // create the buffers
  
          for (let key in attributes) {
            this.addAttribute(key, attributes[key]);
          }
        }
  
        addAttribute(key, attr) {
          this.attributes[key] = attr; // Set options
  
          attr.id = ATTR_ID++; // TODO: currently unused, remove?
  
          attr.size = attr.size || 1;
          attr.type = attr.type || (attr.data.constructor === Float32Array ? this.gl.FLOAT : attr.data.constructor === Uint16Array ? this.gl.UNSIGNED_SHORT : this.gl.UNSIGNED_INT); // Uint32Array
  
          attr.target = key === 'index' ? this.gl.ELEMENT_ARRAY_BUFFER : this.gl.ARRAY_BUFFER;
          attr.normalized = attr.normalized || false;
          attr.stride = attr.stride || 0;
          attr.offset = attr.offset || 0;
          attr.count = attr.count || (attr.stride ? attr.data.byteLength / attr.stride : attr.data.length / attr.size);
          attr.divisor = attr.instanced || 0;
          attr.needsUpdate = false;
  
          if (!attr.buffer) {
            attr.buffer = this.gl.createBuffer(); // Push data to buffer
  
            this.updateAttribute(attr);
          } // Update geometry counts. If indexed, ignore regular attributes
  
  
          if (attr.divisor) {
            this.isInstanced = true;
  
            if (this.instancedCount && this.instancedCount !== attr.count * attr.divisor) {
              console.warn('geometry has multiple instanced buffers of different length');
              return this.instancedCount = Math.min(this.instancedCount, attr.count * attr.divisor);
            }
  
            this.instancedCount = attr.count * attr.divisor;
          } else if (key === 'index') {
            this.drawRange.count = attr.count;
          } else if (!this.attributes.index) {
            this.drawRange.count = Math.max(this.drawRange.count, attr.count);
          }
        }
  
        updateAttribute(attr) {
          if (this.glState.boundBuffer !== attr.buffer) {
            this.gl.bindBuffer(attr.target, attr.buffer);
            this.glState.boundBuffer = attr.buffer;
          }
  
          this.gl.bufferData(attr.target, attr.data, this.gl.STATIC_DRAW);
          attr.needsUpdate = false;
        }
  
        setIndex(value) {
          this.addAttribute('index', value);
        }
  
        setDrawRange(start, count) {
          this.drawRange.start = start;
          this.drawRange.count = count;
        }
  
        setInstancedCount(value) {
          this.instancedCount = value;
        }
  
        createVAO(program) {
          this.VAOs[program.attributeOrder] = this.gl.renderer.createVertexArray();
          this.gl.renderer.bindVertexArray(this.VAOs[program.attributeOrder]);
          this.bindAttributes(program);
        }
  
        bindAttributes(program) {
          // Link all attributes to program using gl.vertexAttribPointer
          program.attributeLocations.forEach((location, {
            name,
            type
          }) => {
            // If geometry missing a required shader attribute
            if (!this.attributes[name]) {
              console.warn(`active attribute ${name} not being supplied`);
              return;
            }
  
            const attr = this.attributes[name];
            this.gl.bindBuffer(attr.target, attr.buffer);
            this.glState.boundBuffer = attr.buffer; // For matrix attributes, buffer needs to be defined per column
  
            let numLoc = 1;
            if (type === 35674) numLoc = 2; // mat2
  
            if (type === 35675) numLoc = 3; // mat3
  
            if (type === 35676) numLoc = 4; // mat4
  
            const size = attr.size / numLoc;
            const stride = numLoc === 1 ? 0 : numLoc * numLoc * numLoc;
            const offset = numLoc === 1 ? 0 : numLoc * numLoc;
  
            for (let i = 0; i < numLoc; i++) {
              this.gl.vertexAttribPointer(location + i, size, attr.type, attr.normalized, attr.stride + stride, attr.offset + i * offset);
              this.gl.enableVertexAttribArray(location + i); // For instanced attributes, divisor needs to be set.
              // For firefox, need to set back to 0 if non-instanced drawn after instanced. Else won't render
  
              this.gl.renderer.vertexAttribDivisor(location + i, attr.divisor);
            }
          }); // Bind indices if geometry indexed
  
          if (this.attributes.index) this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.attributes.index.buffer);
        }
  
        draw({
          program,
          mode = this.gl.TRIANGLES
        }) {
          if (this.gl.renderer.currentGeometry !== `${this.id}_${program.attributeOrder}`) {
            if (!this.VAOs[program.attributeOrder]) this.createVAO(program);
            this.gl.renderer.bindVertexArray(this.VAOs[program.attributeOrder]);
            this.gl.renderer.currentGeometry = `${this.id}_${program.attributeOrder}`;
          } // Check if any attributes need updating
  
  
          program.attributeLocations.forEach((location, {
            name
          }) => {
            const attr = this.attributes[name];
            if (attr.needsUpdate) this.updateAttribute(attr);
          });
  
          if (this.isInstanced) {
            if (this.attributes.index) {
              this.gl.renderer.drawElementsInstanced(mode, this.drawRange.count, this.attributes.index.type, this.attributes.index.offset + this.drawRange.start * 2, this.instancedCount);
            } else {
              this.gl.renderer.drawArraysInstanced(mode, this.drawRange.start, this.drawRange.count, this.instancedCount);
            }
          } else {
            if (this.attributes.index) {
              this.gl.drawElements(mode, this.drawRange.count, this.attributes.index.type, this.attributes.index.offset + this.drawRange.start * 2);
            } else {
              this.gl.drawArrays(mode, this.drawRange.start, this.drawRange.count);
            }
          }
        }
  
        getPositionArray() {
          // Use position buffer, or min/max if available
          const attr = this.attributes.position; // if (attr.min) return [...attr.min, ...attr.max];
  
          if (attr.data) return attr.data;
          if (isBoundsWarned) return;
          console.warn('No position buffer data found to compute bounds');
          return isBoundsWarned = true;
        }
  
        computeBoundingBox(array = null) {
          if (!array) array = this.getPositionArray();
  
          if (!this.bounds) {
            this.bounds = {
              min: new Vec3(),
              max: new Vec3(),
              center: new Vec3(),
              scale: new Vec3(),
              radius: Infinity
            };
          }
  
          const min = this.bounds.min;
          const max = this.bounds.max;
          const center = this.bounds.center;
          const scale = this.bounds.scale;
          min.set(+Infinity);
          max.set(-Infinity); // TODO: use offset/stride if exists
          // TODO: check size of position (eg triangle with Vec2)
  
          for (let i = 0, l = array.length; i < l; i += 3) {
            const x = array[i];
            const y = array[i + 1];
            const z = array[i + 2];
            min.x = Math.min(x, min.x);
            min.y = Math.min(y, min.y);
            min.z = Math.min(z, min.z);
            max.x = Math.max(x, max.x);
            max.y = Math.max(y, max.y);
            max.z = Math.max(z, max.z);
          }
  
          scale.sub(max, min);
          center.add(min, max).divide(2);
        }
  
        computeBoundingSphere(array = null) {
          if (!array) array = this.getPositionArray();
          if (!this.bounds) this.computeBoundingBox(array);
          let maxRadiusSq = 0;
  
          for (let i = 0, l = array.length; i < l; i += 3) {
            tempVec3.fromArray(array, i);
            maxRadiusSq = Math.max(maxRadiusSq, this.bounds.center.squaredDistance(tempVec3));
          }
  
          this.bounds.radius = Math.sqrt(maxRadiusSq);
        }
  
        computeVertexNormals() {
          const positionAttribute = this.attributes['position'];
          if (!positionAttribute) return;
          let normalAttribute = this.attributes['normal'];
  
          if (!normalAttribute) {
            this.addAttribute('normal', {
              size: 3,
              data: new Float32Array(positionAttribute.count * 3)
            });
            normalAttribute = this.attributes['normal'];
          } else {
            normalAttribute.data.fill(0);
          }
  
          const pA = new Vec3(),
                pB = new Vec3(),
                pC = new Vec3();
          const nA = new Vec3(),
                nB = new Vec3(),
                nC = new Vec3();
          const cb = new Vec3(),
                ab = new Vec3();
          const indexAttribute = this.attributes['index'];
  
          if (indexAttribute) {
            let iA, iB, iC;
  
            for (let i = 0, il = indexAttribute.count; i < il; i += 3) {
              iA = indexAttribute.data[i];
              iB = indexAttribute.data[i + 1];
              iC = indexAttribute.data[i + 2]; // copy points
  
              pA.fromArray(positionAttribute.data, iA * positionAttribute.size);
              pB.fromArray(positionAttribute.data, iB * positionAttribute.size);
              pC.fromArray(positionAttribute.data, iC * positionAttribute.size); // cross product two edges to get the face normal
  
              cb.sub(pC, pB);
              ab.sub(pA, pB);
              cb.cross(ab); // read vertex normals 
  
              nA.fromArray(normalAttribute.data, iA * normalAttribute.size);
              nB.fromArray(normalAttribute.data, iB * normalAttribute.size);
              nC.fromArray(normalAttribute.data, iC * normalAttribute.size); // add face normal
  
              nA.add(cb);
              nB.add(cb);
              nC.add(cb); // write back
  
              iA *= normalAttribute.size;
              normalAttribute.data[iA] = nA.x;
              normalAttribute.data[iA + 1] = nA.y;
              normalAttribute.data[iA + 2] = nA.z;
              iB *= normalAttribute.size;
              normalAttribute.data[iB] = nB.x;
              normalAttribute.data[iB + 1] = nB.y;
              normalAttribute.data[iB + 2] = nB.z;
              iC *= normalAttribute.size;
              normalAttribute.data[iC] = nC.x;
              normalAttribute.data[iC + 1] = nC.y;
              normalAttribute.data[iC + 2] = nC.z;
            }
          } else {
            // non-indexed elements (unconnected triangle soup)
            for (let i = 0, il = positionAttribute.count; i < il; i += 3) {
              pA.fromArray(positionAttribute.data, i * positionAttribute.size);
              pB.fromArray(positionAttribute.data, (i + 1) * positionAttribute.size);
              pC.fromArray(positionAttribute.data, (i + 2) * positionAttribute.size);
              cb.sub(pC, pB);
              ab.sub(pA, pB);
              cb.cross(ab);
              normalAttribute.data[i * normalAttribute.size] = cb.x;
              normalAttribute.data[i * normalAttribute.size + 1] = cb.y;
              normalAttribute.data[i * normalAttribute.size + 2] = cb.z;
              normalAttribute.data[(i + 1) * normalAttribute.size] = cb.x;
              normalAttribute.data[(i + 1) * normalAttribute.size + 1] = cb.y;
              normalAttribute.data[(i + 1) * normalAttribute.size + 2] = cb.z;
              normalAttribute.data[(i + 2) * normalAttribute.size] = cb.x;
              normalAttribute.data[(i + 2) * normalAttribute.size + 1] = cb.y;
              normalAttribute.data[(i + 2) * normalAttribute.size + 2] = cb.z;
            }
          }
  
          this.normalizeNormals();
          normalAttribute.needsUpdate = true;
        }
  
        normalizeNormals() {
          const normals = this.attributes.normal;
  
          for (let i = 0, il = normals.count; i < il; i++) {
            tempVec3.fromArray(normals.data, i * normals.size);
            tempVec3.normalize();
            normals.data[i * normals.size] = tempVec3.x;
            normals.data[i * normals.size + 1] = tempVec3.y;
            normals.data[i * normals.size + 2] = tempVec3.z;
          }
        }
  
        remove() {
          // if (this.vao) this.gl.renderer.deleteVertexArray(this.vao);
          for (let key in this.attributes) {
            this.gl.deleteBuffer(this.attributes[key].buffer);
            delete this.attributes[key];
          }
        }
  
      }
  
      // TODO: upload empty texture if null ? maybe not
      // TODO: upload identity matrix if null ?
      // TODO: sampler Cube
      let ID$1 = 1; // cache of typed arrays used to flatten uniform arrays
  
      const arrayCacheF32 = {};
      class Program {
        constructor(gl, {
          vertex,
          fragment,
          uniforms = {},
          transparent = false,
          cullFace = gl.BACK,
          frontFace = gl.CCW,
          depthTest = true,
          depthWrite = true,
          depthFunc = gl.LESS
        } = {}) {
          this.gl = void 0;
          this.uniforms = void 0;
          this.id = void 0;
          this.transparent = void 0;
          this.cullFace = void 0;
          this.frontFace = void 0;
          this.depthTest = void 0;
          this.depthWrite = void 0;
          this.depthFunc = void 0;
          this.blendFunc = void 0;
          this.blendEquation = void 0;
          this.program = void 0;
          this.uniformLocations = void 0;
          this.attributeLocations = void 0;
          this.attributeOrder = void 0;
          this.gltfMaterial = void 0;
          if (!gl.canvas) console.error('gl not passed as fist argument to Program');
          this.gl = gl;
          this.uniforms = uniforms;
          this.id = ID$1++;
          if (!vertex) console.warn('vertex shader not supplied');
          if (!fragment) console.warn('fragment shader not supplied'); // Store program state
  
          this.transparent = transparent;
          this.cullFace = cullFace;
          this.frontFace = frontFace;
          this.depthTest = depthTest;
          this.depthWrite = depthWrite;
          this.depthFunc = depthFunc;
          this.blendFunc = {};
          this.blendEquation = {}; // set default blendFunc if transparent flagged
  
          if (this.transparent && !this.blendFunc.src) {
            if (this.gl.renderer.premultipliedAlpha) this.setBlendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);else this.setBlendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
          } // compile vertex shader and log errors
  
  
          const vertexShader = gl.createShader(gl.VERTEX_SHADER);
          gl.shaderSource(vertexShader, vertex);
          gl.compileShader(vertexShader);
  
          if (gl.getShaderInfoLog(vertexShader) !== '') {
            console.warn(`${gl.getShaderInfoLog(vertexShader)}\nVertex Shader\n${addLineNumbers(vertex)}`);
          } // compile fragment shader and log errors
  
  
          const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
          gl.shaderSource(fragmentShader, fragment);
          gl.compileShader(fragmentShader);
  
          if (gl.getShaderInfoLog(fragmentShader) !== '') {
            console.warn(`${gl.getShaderInfoLog(fragmentShader)}\nFragment Shader\n${addLineNumbers(fragment)}`);
          } // compile program and log errors
  
  
          this.program = gl.createProgram();
          gl.attachShader(this.program, vertexShader);
          gl.attachShader(this.program, fragmentShader);
          gl.linkProgram(this.program);
  
          if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
            console.warn(gl.getProgramInfoLog(this.program));
            return;
          } // Remove shader once linked
  
  
          gl.deleteShader(vertexShader);
          gl.deleteShader(fragmentShader); // Get active uniform locations
  
          this.uniformLocations = new Map();
          let numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
  
          for (let uIndex = 0; uIndex < numUniforms; uIndex++) {
            let uniform = gl.getActiveUniform(this.program, uIndex);
            this.uniformLocations.set(uniform, gl.getUniformLocation(this.program, uniform.name)); // split uniforms' names to separate array and struct declarations
  
            const split = uniform.name.match(/(\w+)/g);
            uniform.uniformName = split[0];
  
            if (split.length === 3) {
              uniform.isStructArray = true;
              uniform.structIndex = Number(split[1]);
              uniform.structProperty = split[2];
            } else if (split.length === 2 && isNaN(Number(split[1]))) {
              uniform.isStruct = true;
              uniform.structProperty = split[1];
            }
          } // Get active attribute locations
  
  
          this.attributeLocations = new Map();
          const locations = [];
          const numAttribs = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
  
          for (let aIndex = 0; aIndex < numAttribs; aIndex++) {
            const attribute = gl.getActiveAttrib(this.program, aIndex);
            const location = gl.getAttribLocation(this.program, attribute.name);
            locations[location] = attribute.name;
            this.attributeLocations.set(attribute, location);
          }
  
          this.attributeOrder = locations.join('');
        }
  
        setBlendFunc(src, dst, srcAlpha, dstAlpha) {
          this.blendFunc.src = src;
          this.blendFunc.dst = dst;
          this.blendFunc.srcAlpha = srcAlpha;
          this.blendFunc.dstAlpha = dstAlpha;
          if (src) this.transparent = true;
        }
  
        setBlendEquation(modeRGB, modeAlpha) {
          this.blendEquation.modeRGB = modeRGB;
          this.blendEquation.modeAlpha = modeAlpha;
        }
  
        applyState() {
          if (this.depthTest) this.gl.renderer.enable(this.gl.DEPTH_TEST);else this.gl.renderer.disable(this.gl.DEPTH_TEST);
          if (this.cullFace) this.gl.renderer.enable(this.gl.CULL_FACE);else this.gl.renderer.disable(this.gl.CULL_FACE);
          if (this.blendFunc.src) this.gl.renderer.enable(this.gl.BLEND);else this.gl.renderer.disable(this.gl.BLEND);
          if (this.cullFace) this.gl.renderer.setCullFace(this.cullFace);
          this.gl.renderer.setFrontFace(this.frontFace);
          this.gl.renderer.setDepthMask(this.depthWrite);
          this.gl.renderer.setDepthFunc(this.depthFunc);
          if (this.blendFunc.src) this.gl.renderer.setBlendFunc(this.blendFunc.src, this.blendFunc.dst, this.blendFunc.srcAlpha, this.blendFunc.dstAlpha);
          this.gl.renderer.setBlendEquation(this.blendEquation.modeRGB, this.blendEquation.modeAlpha);
        }
  
        use({
          flipFaces = false
        } = {}) {
          let textureUnit = -1;
          const programActive = this.gl.renderer.currentProgram === this.id; // Avoid gl call if program already in use
  
          if (!programActive) {
            this.gl.useProgram(this.program);
            this.gl.renderer.currentProgram = this.id;
          } // Set only the active uniforms found in the shader
  
  
          this.uniformLocations.forEach((location, activeUniform) => {
            let name = activeUniform.uniformName; // get supplied uniform
  
            let uniform = this.uniforms[name]; // For structs, get the specific property instead of the entire object
  
            if (activeUniform.isStruct) {
              uniform = uniform[activeUniform.structProperty];
              name += `.${activeUniform.structProperty}`;
            }
  
            if (activeUniform.isStructArray) {
              uniform = uniform[activeUniform.structIndex][activeUniform.structProperty];
              name += `[${activeUniform.structIndex}].${activeUniform.structProperty}`;
            }
  
            if (!uniform) {
              return warn(`Active uniform ${name} has not been supplied`);
            }
  
            if (uniform && uniform.value === undefined) {
              return warn(`${name} uniform is missing a value parameter`);
            }
  
            if (uniform.value.texture) {
              textureUnit = textureUnit + 1; // Check if texture needs to be updated
  
              uniform.value.update(textureUnit);
              return setUniform(this.gl, activeUniform.type, location, textureUnit);
            } // For texture arrays, set uniform as an array of texture units instead of just one
  
  
            if (uniform.value.length && uniform.value[0].texture) {
              const textureUnits = [];
              uniform.value.forEach(value => {
                textureUnit = textureUnit + 1;
                value.update(textureUnit);
                textureUnits.push(textureUnit);
              });
              return setUniform(this.gl, activeUniform.type, location, textureUnits);
            }
  
            setUniform(this.gl, activeUniform.type, location, uniform.value);
          });
          this.applyState();
          if (flipFaces) this.gl.renderer.setFrontFace(this.frontFace === this.gl.CCW ? this.gl.CW : this.gl.CCW);
        }
  
        remove() {
          this.gl.deleteProgram(this.program);
        }
  
      }
  
      function setUniform(gl, type, location, value) {
        value = value.length ? flatten(value) : value;
        const setValue = gl.renderer.state.uniformLocations.get(location); // Avoid redundant uniform commands
  
        if (value.length) {
          if (setValue === undefined || setValue.length !== value.length) {
            // clone array to store as cache
            gl.renderer.state.uniformLocations.set(location, value.slice(0));
          } else {
            if (arraysEqual(setValue, value)) return; // Update cached array values
  
            setValue.set ? setValue.set(value) : setArray(setValue, value);
            gl.renderer.state.uniformLocations.set(location, setValue);
          }
        } else {
          if (setValue === value) return;
          gl.renderer.state.uniformLocations.set(location, value);
        }
  
        switch (type) {
          case 5126:
            return value.length ? gl.uniform1fv(location, value) : gl.uniform1f(location, value);
          // FLOAT
  
          case 35664:
            return gl.uniform2fv(location, value);
          // FLOAT_VEC2
  
          case 35665:
            return gl.uniform3fv(location, value);
          // FLOAT_VEC3
  
          case 35666:
            return gl.uniform4fv(location, value);
          // FLOAT_VEC4
  
          case 35670: // BOOL
  
          case 5124: // INT
  
          case 35678: // SAMPLER_2D
  
          case 35680:
            return value.length ? gl.uniform1iv(location, value) : gl.uniform1i(location, value);
          // SAMPLER_CUBE
  
          case 35671: // BOOL_VEC2
  
          case 35667:
            return gl.uniform2iv(location, value);
          // INT_VEC2
  
          case 35672: // BOOL_VEC3
  
          case 35668:
            return gl.uniform3iv(location, value);
          // INT_VEC3
  
          case 35673: // BOOL_VEC4
  
          case 35669:
            return gl.uniform4iv(location, value);
          // INT_VEC4
  
          case 35674:
            return gl.uniformMatrix2fv(location, false, value);
          // FLOAT_MAT2
  
          case 35675:
            return gl.uniformMatrix3fv(location, false, value);
          // FLOAT_MAT3
  
          case 35676:
            return gl.uniformMatrix4fv(location, false, value);
          // FLOAT_MAT4
        }
      }
  
      function addLineNumbers(string) {
        let lines = string.split('\n');
  
        for (let i = 0; i < lines.length; i++) {
          lines[i] = i + 1 + ': ' + lines[i];
        }
  
        return lines.join('\n');
      }
  
      function flatten(a) {
        const arrayLen = a.length;
        const valueLen = a[0].length;
        if (valueLen === undefined) return a;
        const length = arrayLen * valueLen;
        let value = arrayCacheF32[length];
        if (!value) arrayCacheF32[length] = value = new Float32Array(length);
  
        for (let i = 0; i < arrayLen; i++) value.set(a[i], i * valueLen);
  
        return value;
      }
  
      function arraysEqual(a, b) {
        if (a.length !== b.length) return false;
  
        for (let i = 0, l = a.length; i < l; i++) {
          if (a[i] !== b[i]) return false;
        }
  
        return true;
      }
  
      function setArray(a, b) {
        for (let i = 0, l = a.length; i < l; i++) {
          a[i] = b[i];
        }
      }
  
      let warnCount = 0;
  
      function warn(message) {
        if (warnCount > 100) return;
        console.warn(message);
        warnCount++;
        if (warnCount > 100) console.warn('More than 100 program warnings - stopping logs.');
      }
  
      // Not automatic - devs to use these methods manually
      // gl.colorMask( colorMask, colorMask, colorMask, colorMask );
      // gl.clearColor( r, g, b, a );
      // gl.stencilMask( stencilMask );
      // gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
      // gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
      // gl.clearStencil( stencil );
  
      const tempVec3$1 = new Vec3();
      let ID$2 = 1;
      class Renderer {
        get id() {
          return this._id;
        }
  
        constructor({
          canvas = document.createElement('canvas'),
          width = 300,
          height = 150,
          dpr = 1,
          alpha = false,
          depth = true,
          stencil = false,
          antialias = false,
          premultipliedAlpha = false,
          preserveDrawingBuffer = false,
          powerPreference = 'default',
          autoClear = true,
          webgl = 2
        } = {}) {
          this.dpr = void 0;
          this.alpha = void 0;
          this.color = void 0;
          this.depth = void 0;
          this.stencil = void 0;
          this.premultipliedAlpha = void 0;
          this.autoClear = void 0;
          this.gl = void 0;
          this.isWebgl2 = void 0;
          this.width = void 0;
          this.height = void 0;
          this.parameters = void 0;
          this.state = void 0;
          this.extensions = void 0;
          this.vertexAttribDivisor = void 0;
          this.drawArraysInstanced = void 0;
          this.drawElementsInstanced = void 0;
          this.createVertexArray = void 0;
          this.bindVertexArray = void 0;
          this.deleteVertexArray = void 0;
          this.drawBuffers = void 0;
          this.currentProgram = void 0;
          this.currentGeometry = void 0;
          this._id = void 0;
          const attributes = {
            alpha,
            depth,
            stencil,
            antialias,
            premultipliedAlpha,
            preserveDrawingBuffer,
            powerPreference
          };
          this.dpr = dpr;
          this.alpha = alpha;
          this.color = true;
          this.depth = depth;
          this.stencil = stencil;
          this.premultipliedAlpha = premultipliedAlpha;
          this.autoClear = autoClear;
          this._id = ID$2++; // Attempt WebGL2 unless forced to 1, if not supported fallback to WebGL1
  
          if (webgl === 2) this.gl = canvas.getContext('webgl2', attributes);
          this.isWebgl2 = !!this.gl;
  
          if (!this.gl) {
            this.gl = canvas.getContext('webgl', attributes) || canvas.getContext('experimental-webgl', attributes);
          } // Attach renderer to gl so that all classes have access to internal state functions
  
  
          this.gl.renderer = this; // initialise size values
  
          this.setSize(width, height); // gl state stores to avoid redundant calls on methods used internally
  
          this.state = {};
          this.state.blendFunc = {
            src: this.gl.ONE,
            dst: this.gl.ZERO
          };
          this.state.blendEquation = {
            modeRGB: this.gl.FUNC_ADD
          };
          this.state.cullFace = null;
          this.state.frontFace = this.gl.CCW;
          this.state.depthMask = true;
          this.state.depthFunc = this.gl.LESS;
          this.state.premultiplyAlpha = false;
          this.state.flipY = false;
          this.state.unpackAlignment = 4;
          this.state.framebuffer = null;
          this.state.viewport = {
            width: null,
            height: null
          };
          this.state.textureUnits = [];
          this.state.activeTextureUnit = 0;
          this.state.boundBuffer = null;
          this.state.uniformLocations = new Map(); // store requested extensions
  
          this.extensions = {}; // Initialise extra format types
  
          if (this.isWebgl2) {
            this.getExtension('EXT_color_buffer_float');
            this.getExtension('OES_texture_float_linear');
          } else {
            this.getExtension('OES_texture_float');
            this.getExtension('OES_texture_float_linear');
            this.getExtension('OES_texture_half_float');
            this.getExtension('OES_texture_half_float_linear');
            this.getExtension('OES_element_index_uint');
            this.getExtension('OES_standard_derivatives');
            this.getExtension('EXT_sRGB');
            this.getExtension('WEBGL_depth_texture');
            this.getExtension('WEBGL_draw_buffers');
          } // Create method aliases using extension (WebGL1) or native if available (WebGL2)
  
  
          this.vertexAttribDivisor = this.getExtension('ANGLE_instanced_arrays', 'vertexAttribDivisor', 'vertexAttribDivisorANGLE');
          this.drawArraysInstanced = this.getExtension('ANGLE_instanced_arrays', 'drawArraysInstanced', 'drawArraysInstancedANGLE');
          this.drawElementsInstanced = this.getExtension('ANGLE_instanced_arrays', 'drawElementsInstanced', 'drawElementsInstancedANGLE');
          this.createVertexArray = this.getExtension('OES_vertex_array_object', 'createVertexArray', 'createVertexArrayOES');
          this.bindVertexArray = this.getExtension('OES_vertex_array_object', 'bindVertexArray', 'bindVertexArrayOES');
          this.deleteVertexArray = this.getExtension('OES_vertex_array_object', 'deleteVertexArray', 'deleteVertexArrayOES');
          this.drawBuffers = this.getExtension('WEBGL_draw_buffers', 'drawBuffers', 'drawBuffersWEBGL'); // Store device parameters
  
          this.parameters = {};
          this.parameters.maxTextureUnits = this.gl.getParameter(this.gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
          this.parameters.maxAnisotropy = this.getExtension('EXT_texture_filter_anisotropic') ? this.gl.getParameter(this.getExtension('EXT_texture_filter_anisotropic').MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 0;
        }
  
        setSize(width, height) {
          this.width = width;
          this.height = height;
          this.gl.canvas.width = width * this.dpr;
          this.gl.canvas.height = height * this.dpr;
          Object.assign(this.gl.canvas.style, {
            width: width + 'px',
            height: height + 'px'
          });
        }
  
        setViewport(width, height) {
          if (this.state.viewport.width === width && this.state.viewport.height === height) return;
          this.state.viewport.width = width;
          this.state.viewport.height = height;
          this.gl.viewport(0, 0, width, height);
        }
  
        enable(id) {
          if (this.state[id] === true) return;
          this.gl.enable(id);
          this.state[id] = true;
        }
  
        disable(id) {
          if (this.state[id] === false) return;
          this.gl.disable(id);
          this.state[id] = false;
        }
  
        setBlendFunc(src, dst, srcAlpha, dstAlpha) {
          if (this.state.blendFunc.src === src && this.state.blendFunc.dst === dst && this.state.blendFunc.srcAlpha === srcAlpha && this.state.blendFunc.dstAlpha === dstAlpha) return;
          this.state.blendFunc.src = src;
          this.state.blendFunc.dst = dst;
          this.state.blendFunc.srcAlpha = srcAlpha;
          this.state.blendFunc.dstAlpha = dstAlpha;
          if (srcAlpha !== undefined) this.gl.blendFuncSeparate(src, dst, srcAlpha, dstAlpha);else this.gl.blendFunc(src, dst);
        }
  
        setBlendEquation(modeRGB, modeAlpha) {
          modeRGB = modeRGB || this.gl.FUNC_ADD;
          if (this.state.blendEquation.modeRGB === modeRGB && this.state.blendEquation.modeAlpha === modeAlpha) return;
          this.state.blendEquation.modeRGB = modeRGB;
          this.state.blendEquation.modeAlpha = modeAlpha;
          if (modeAlpha !== undefined) this.gl.blendEquationSeparate(modeRGB, modeAlpha);else this.gl.blendEquation(modeRGB);
        }
  
        setCullFace(value) {
          if (this.state.cullFace === value) return;
          this.state.cullFace = value;
          this.gl.cullFace(value);
        }
  
        setFrontFace(value) {
          if (this.state.frontFace === value) return;
          this.state.frontFace = value;
          this.gl.frontFace(value);
        }
  
        setDepthMask(value) {
          if (this.state.depthMask === value) return;
          this.state.depthMask = value;
          this.gl.depthMask(value);
        }
  
        setDepthFunc(value) {
          if (this.state.depthFunc === value) return;
          this.state.depthFunc = value;
          this.gl.depthFunc(value);
        }
  
        activeTexture(value) {
          if (this.state.activeTextureUnit === value) return;
          this.state.activeTextureUnit = value;
          this.gl.activeTexture(this.gl.TEXTURE0 + value);
        }
  
        bindFramebuffer({
          target = this.gl.FRAMEBUFFER,
          buffer = null
        } = {}) {
          if (this.state.framebuffer === buffer) return;
          this.state.framebuffer = buffer;
          this.gl.bindFramebuffer(target, buffer);
        }
  
        getExtension(extension, webgl2Func, extFunc) {
          // if webgl2 function supported, return func bound to gl context
          if (webgl2Func && this.gl[webgl2Func]) return this.gl[webgl2Func].bind(this.gl); // fetch extension once only
  
          if (!this.extensions[extension]) {
            this.extensions[extension] = this.gl.getExtension(extension);
          } // return extension if no function requested
  
  
          if (!webgl2Func) return this.extensions[extension]; // Return null if extension not supported
  
          if (!this.extensions[extension]) return null; // return extension function, bound to extension
  
          return this.extensions[extension][extFunc].bind(this.extensions[extension]);
        }
  
        sortOpaque(a, b) {
          if (a.renderOrder !== b.renderOrder) {
            return a.renderOrder - b.renderOrder;
          } else if (a.program.id !== b.program.id) {
            return a.program.id - b.program.id;
          } else if (a.zDepth !== b.zDepth) {
            return a.zDepth - b.zDepth;
          } else {
            return b.id - a.id;
          }
        }
  
        sortTransparent(a, b) {
          if (a.renderOrder !== b.renderOrder) {
            return a.renderOrder - b.renderOrder;
          }
  
          if (a.zDepth !== b.zDepth) {
            return b.zDepth - a.zDepth;
          } else {
            return b.id - a.id;
          }
        }
  
        sortUI(a, b) {
          if (a.renderOrder !== b.renderOrder) {
            return a.renderOrder - b.renderOrder;
          } else if (a.program.id !== b.program.id) {
            return a.program.id - b.program.id;
          } else {
            return b.id - a.id;
          }
        }
  
        getRenderList({
          scene,
          camera,
          frustumCull,
          sort
        }) {
          let renderList = [];
          if (camera && frustumCull) camera.updateFrustum(); // Get visible
  
          scene.traverse(node => {
            if (!node.visible) return true;
            if (!isMesh(node)) return; // if (!node.draw) return;
  
            if (frustumCull && node.frustumCulled && camera) {
              if (!camera.frustumIntersectsMesh(node)) return;
            }
  
            renderList.push(node);
          });
  
          if (sort) {
            const opaque = [];
            const transparent = []; // depthTest true
  
            const ui = []; // depthTest false
  
            renderList.forEach(node => {
              // Split into the 3 render groups
              if (!node.program.transparent) {
                opaque.push(node);
              } else if (node.program.depthTest) {
                transparent.push(node);
              } else {
                ui.push(node);
              }
  
              node.zDepth = 0; // Only calculate z-depth if renderOrder unset and depthTest is true
  
              if (node.renderOrder !== 0 || !node.program.depthTest || !camera) return; // update z-depth
  
              node.worldMatrix.getTranslation(tempVec3$1);
              tempVec3$1.applyMatrix4(camera.projectionViewMatrix);
              node.zDepth = tempVec3$1.z;
            });
            opaque.sort(this.sortOpaque);
            transparent.sort(this.sortTransparent);
            ui.sort(this.sortUI);
            renderList = opaque.concat(transparent, ui);
          }
  
          return renderList;
        }
  
        render({
          scene,
          camera,
          target = null,
          update = true,
          sort = true,
          frustumCull = true,
          clear
        }) {
          if (target === null) {
            // make sure no render target bound so draws to canvas
            this.bindFramebuffer();
            this.setViewport(this.width * this.dpr, this.height * this.dpr);
          } else {
            // bind supplied render target and update viewport
            this.bindFramebuffer(target);
            this.setViewport(target.width, target.height);
          }
  
          if (clear || this.autoClear && clear !== false) {
            // Ensure depth buffer writing is enabled so it can be cleared
            if (this.depth && (!target || target.depth)) {
              this.enable(this.gl.DEPTH_TEST);
              this.setDepthMask(true);
            }
  
            this.gl.clear((this.color ? this.gl.COLOR_BUFFER_BIT : 0) | (this.depth ? this.gl.DEPTH_BUFFER_BIT : 0) | (this.stencil ? this.gl.STENCIL_BUFFER_BIT : 0));
          } // updates all scene graph matrices
  
  
          if (update) scene.updateMatrixWorld(); // Update camera separately, in case not in scene graph
  
          if (camera) camera.updateMatrixWorld(); // Get render list - entails culling and sorting
  
          const renderList = this.getRenderList({
            scene,
            camera,
            frustumCull,
            sort
          });
          renderList.forEach(node => {
            node.draw({
              camera
            });
          });
        }
  
      }
  
      const EPSILON = 0.000001;
      /**
       * Copy the values from one mat4 to another
       *
       * @param {mat4} out the receiving matrix
       * @param {mat4} a the source matrix
       * @returns {mat4} out
       */
  
      function copy$1(out, a) {
        out[0] = a[0];
        out[1] = a[1];
        out[2] = a[2];
        out[3] = a[3];
        out[4] = a[4];
        out[5] = a[5];
        out[6] = a[6];
        out[7] = a[7];
        out[8] = a[8];
        out[9] = a[9];
        out[10] = a[10];
        out[11] = a[11];
        out[12] = a[12];
        out[13] = a[13];
        out[14] = a[14];
        out[15] = a[15];
        return out;
      }
      /**
       * Set the components of a mat4 to the given values
       *
       * @param {mat4} out the receiving matrix
       * @returns {mat4} out
       */
  
      function set$1(out, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
        out[0] = m00;
        out[1] = m01;
        out[2] = m02;
        out[3] = m03;
        out[4] = m10;
        out[5] = m11;
        out[6] = m12;
        out[7] = m13;
        out[8] = m20;
        out[9] = m21;
        out[10] = m22;
        out[11] = m23;
        out[12] = m30;
        out[13] = m31;
        out[14] = m32;
        out[15] = m33;
        return out;
      }
      /**
       * Set a mat4 to the identity matrix
       *
       * @param {mat4} out the receiving matrix
       * @returns {mat4} out
       */
  
      function identity(out) {
        out[0] = 1;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = 0;
        out[5] = 1;
        out[6] = 0;
        out[7] = 0;
        out[8] = 0;
        out[9] = 0;
        out[10] = 1;
        out[11] = 0;
        out[12] = 0;
        out[13] = 0;
        out[14] = 0;
        out[15] = 1;
        return out;
      }
      /**
       * Inverts a mat4
       *
       * @param {mat4} out the receiving matrix
       * @param {mat4} a the source matrix
       * @returns {mat4} out
       */
  
      function invert(out, a) {
        let a00 = a[0],
            a01 = a[1],
            a02 = a[2],
            a03 = a[3];
        let a10 = a[4],
            a11 = a[5],
            a12 = a[6],
            a13 = a[7];
        let a20 = a[8],
            a21 = a[9],
            a22 = a[10],
            a23 = a[11];
        let a30 = a[12],
            a31 = a[13],
            a32 = a[14],
            a33 = a[15];
        let b00 = a00 * a11 - a01 * a10;
        let b01 = a00 * a12 - a02 * a10;
        let b02 = a00 * a13 - a03 * a10;
        let b03 = a01 * a12 - a02 * a11;
        let b04 = a01 * a13 - a03 * a11;
        let b05 = a02 * a13 - a03 * a12;
        let b06 = a20 * a31 - a21 * a30;
        let b07 = a20 * a32 - a22 * a30;
        let b08 = a20 * a33 - a23 * a30;
        let b09 = a21 * a32 - a22 * a31;
        let b10 = a21 * a33 - a23 * a31;
        let b11 = a22 * a33 - a23 * a32; // Calculate the determinant
  
        let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
  
        if (!det) {
          return null;
        }
  
        det = 1.0 / det;
        out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
        out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
        out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
        out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
        out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
        out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
        out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
        out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
        out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
        out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
        out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
        out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
        out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
        out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
        out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
        out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
        return out;
      }
      /**
       * Calculates the determinant of a mat4
       *
       * @param {mat4} a the source matrix
       * @returns {Number} determinant of a
       */
  
      function determinant(a) {
        let a00 = a[0],
            a01 = a[1],
            a02 = a[2],
            a03 = a[3];
        let a10 = a[4],
            a11 = a[5],
            a12 = a[6],
            a13 = a[7];
        let a20 = a[8],
            a21 = a[9],
            a22 = a[10],
            a23 = a[11];
        let a30 = a[12],
            a31 = a[13],
            a32 = a[14],
            a33 = a[15];
        let b00 = a00 * a11 - a01 * a10;
        let b01 = a00 * a12 - a02 * a10;
        let b02 = a00 * a13 - a03 * a10;
        let b03 = a01 * a12 - a02 * a11;
        let b04 = a01 * a13 - a03 * a11;
        let b05 = a02 * a13 - a03 * a12;
        let b06 = a20 * a31 - a21 * a30;
        let b07 = a20 * a32 - a22 * a30;
        let b08 = a20 * a33 - a23 * a30;
        let b09 = a21 * a32 - a22 * a31;
        let b10 = a21 * a33 - a23 * a31;
        let b11 = a22 * a33 - a23 * a32; // Calculate the determinant
  
        return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
      }
      /**
       * Multiplies two mat4s
       *
       * @param {mat4} out the receiving matrix
       * @param {mat4} a the first operand
       * @param {mat4} b the second operand
       * @returns {mat4} out
       */
  
      function multiply$1(out, a, b) {
        let a00 = a[0],
            a01 = a[1],
            a02 = a[2],
            a03 = a[3];
        let a10 = a[4],
            a11 = a[5],
            a12 = a[6],
            a13 = a[7];
        let a20 = a[8],
            a21 = a[9],
            a22 = a[10],
            a23 = a[11];
        let a30 = a[12],
            a31 = a[13],
            a32 = a[14],
            a33 = a[15]; // Cache only the current line of the second matrix
  
        let b0 = b[0],
            b1 = b[1],
            b2 = b[2],
            b3 = b[3];
        out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
        out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
        out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
        out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
        b0 = b[4];
        b1 = b[5];
        b2 = b[6];
        b3 = b[7];
        out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
        out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
        out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
        out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
        b0 = b[8];
        b1 = b[9];
        b2 = b[10];
        b3 = b[11];
        out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
        out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
        out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
        out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
        b0 = b[12];
        b1 = b[13];
        b2 = b[14];
        b3 = b[15];
        out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
        out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
        out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
        out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
        return out;
      }
      /**
       * Translate a mat4 by the given vector
       *
       * @param {mat4} out the receiving matrix
       * @param {mat4} a the matrix to translate
       * @param {vec3} v vector to translate by
       * @returns {mat4} out
       */
  
      function translate(out, a, v) {
        let x = v[0],
            y = v[1],
            z = v[2];
        let a00, a01, a02, a03;
        let a10, a11, a12, a13;
        let a20, a21, a22, a23;
  
        if (a === out) {
          out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
          out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
          out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
          out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
        } else {
          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];
          out[0] = a00;
          out[1] = a01;
          out[2] = a02;
          out[3] = a03;
          out[4] = a10;
          out[5] = a11;
          out[6] = a12;
          out[7] = a13;
          out[8] = a20;
          out[9] = a21;
          out[10] = a22;
          out[11] = a23;
          out[12] = a00 * x + a10 * y + a20 * z + a[12];
          out[13] = a01 * x + a11 * y + a21 * z + a[13];
          out[14] = a02 * x + a12 * y + a22 * z + a[14];
          out[15] = a03 * x + a13 * y + a23 * z + a[15];
        }
  
        return out;
      }
      /**
       * Scales the mat4 by the dimensions in the given vec3 not using vectorization
       *
       * @param {mat4} out the receiving matrix
       * @param {mat4} a the matrix to scale
       * @param {vec3} v the vec3 to scale the matrix by
       * @returns {mat4} out
       **/
  
      function scale$1(out, a, v) {
        let x = v[0],
            y = v[1],
            z = v[2];
        out[0] = a[0] * x;
        out[1] = a[1] * x;
        out[2] = a[2] * x;
        out[3] = a[3] * x;
        out[4] = a[4] * y;
        out[5] = a[5] * y;
        out[6] = a[6] * y;
        out[7] = a[7] * y;
        out[8] = a[8] * z;
        out[9] = a[9] * z;
        out[10] = a[10] * z;
        out[11] = a[11] * z;
        out[12] = a[12];
        out[13] = a[13];
        out[14] = a[14];
        out[15] = a[15];
        return out;
      }
      /**
       * Rotates a mat4 by the given angle around the given axis
       *
       * @param {mat4} out the receiving matrix
       * @param {mat4} a the matrix to rotate
       * @param {Number} rad the angle to rotate the matrix by
       * @param {vec3} axis the axis to rotate around
       * @returns {mat4} out
       */
  
      function rotate(out, a, rad, axis) {
        let x = axis[0],
            y = axis[1],
            z = axis[2];
        let len = Math.hypot(x, y, z);
        let s, c, t;
        let a00, a01, a02, a03;
        let a10, a11, a12, a13;
        let a20, a21, a22, a23;
        let b00, b01, b02;
        let b10, b11, b12;
        let b20, b21, b22;
  
        if (Math.abs(len) < EPSILON) {
          return null;
        }
  
        len = 1 / len;
        x *= len;
        y *= len;
        z *= len;
        s = Math.sin(rad);
        c = Math.cos(rad);
        t = 1 - c;
        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]; // Construct the elements of the rotation matrix
  
        b00 = x * x * t + c;
        b01 = y * x * t + z * s;
        b02 = z * x * t - y * s;
        b10 = x * y * t - z * s;
        b11 = y * y * t + c;
        b12 = z * y * t + x * s;
        b20 = x * z * t + y * s;
        b21 = y * z * t - x * s;
        b22 = z * z * t + c; // Perform rotation-specific matrix multiplication
  
        out[0] = a00 * b00 + a10 * b01 + a20 * b02;
        out[1] = a01 * b00 + a11 * b01 + a21 * b02;
        out[2] = a02 * b00 + a12 * b01 + a22 * b02;
        out[3] = a03 * b00 + a13 * b01 + a23 * b02;
        out[4] = a00 * b10 + a10 * b11 + a20 * b12;
        out[5] = a01 * b10 + a11 * b11 + a21 * b12;
        out[6] = a02 * b10 + a12 * b11 + a22 * b12;
        out[7] = a03 * b10 + a13 * b11 + a23 * b12;
        out[8] = a00 * b20 + a10 * b21 + a20 * b22;
        out[9] = a01 * b20 + a11 * b21 + a21 * b22;
        out[10] = a02 * b20 + a12 * b21 + a22 * b22;
        out[11] = a03 * b20 + a13 * b21 + a23 * b22;
  
        if (a !== out) {
          // If the source and destination differ, copy the unchanged last row
          out[12] = a[12];
          out[13] = a[13];
          out[14] = a[14];
          out[15] = a[15];
        }
  
        return out;
      }
      /**
       * Returns the translation vector component of a transformation
       *  matrix. If a matrix is built with fromRotationTranslation,
       *  the returned vector will be the same as the translation vector
       *  originally supplied.
       * @param  {vec3} out Vector to receive translation component
       * @param  {mat4} mat Matrix to be decomposed (input)
       * @return {vec3} out
       */
  
      function getTranslation(out, mat) {
        out[0] = mat[12];
        out[1] = mat[13];
        out[2] = mat[14];
        return out;
      }
      /**
       * Returns the scaling factor component of a transformation
       *  matrix. If a matrix is built with fromRotationTranslationScale
       *  with a normalized Quaternion paramter, the returned vector will be
       *  the same as the scaling vector
       *  originally supplied.
       * @param  {vec3} out Vector to receive scaling factor component
       * @param  {mat4} mat Matrix to be decomposed (input)
       * @return {vec3} out
       */
  
      function getScaling(out, mat) {
        let m11 = mat[0];
        let m12 = mat[1];
        let m13 = mat[2];
        let m21 = mat[4];
        let m22 = mat[5];
        let m23 = mat[6];
        let m31 = mat[8];
        let m32 = mat[9];
        let m33 = mat[10];
        out[0] = Math.hypot(m11, m12, m13);
        out[1] = Math.hypot(m21, m22, m23);
        out[2] = Math.hypot(m31, m32, m33);
        return out;
      }
      function getMaxScaleOnAxis(mat) {
        let m11 = mat[0];
        let m12 = mat[1];
        let m13 = mat[2];
        let m21 = mat[4];
        let m22 = mat[5];
        let m23 = mat[6];
        let m31 = mat[8];
        let m32 = mat[9];
        let m33 = mat[10];
        const x = m11 * m11 + m12 * m12 + m13 * m13;
        const y = m21 * m21 + m22 * m22 + m23 * m23;
        const z = m31 * m31 + m32 * m32 + m33 * m33;
        return Math.sqrt(Math.max(x, y, z));
      }
      /**
       * Returns a quaternion representing the rotational component
       *  of a transformation matrix. If a matrix is built with
       *  fromRotationTranslation, the returned quaternion will be the
       *  same as the quaternion originally supplied.
       * @param {quat} out Quaternion to receive the rotation component
       * @param {mat4} mat Matrix to be decomposed (input)
       * @return {quat} out
       */
  
      const getRotation = function () {
        const temp = [0, 0, 0];
        return function (out, mat) {
          let scaling = temp;
          getScaling(scaling, mat);
          let is1 = 1 / scaling[0];
          let is2 = 1 / scaling[1];
          let is3 = 1 / scaling[2];
          let sm11 = mat[0] * is1;
          let sm12 = mat[1] * is2;
          let sm13 = mat[2] * is3;
          let sm21 = mat[4] * is1;
          let sm22 = mat[5] * is2;
          let sm23 = mat[6] * is3;
          let sm31 = mat[8] * is1;
          let sm32 = mat[9] * is2;
          let sm33 = mat[10] * is3;
          let trace = sm11 + sm22 + sm33;
          let S = 0;
  
          if (trace > 0) {
            S = Math.sqrt(trace + 1.0) * 2;
            out[3] = 0.25 * S;
            out[0] = (sm23 - sm32) / S;
            out[1] = (sm31 - sm13) / S;
            out[2] = (sm12 - sm21) / S;
          } else if (sm11 > sm22 && sm11 > sm33) {
            S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2;
            out[3] = (sm23 - sm32) / S;
            out[0] = 0.25 * S;
            out[1] = (sm12 + sm21) / S;
            out[2] = (sm31 + sm13) / S;
          } else if (sm22 > sm33) {
            S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2;
            out[3] = (sm31 - sm13) / S;
            out[0] = (sm12 + sm21) / S;
            out[1] = 0.25 * S;
            out[2] = (sm23 + sm32) / S;
          } else {
            S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2;
            out[3] = (sm12 - sm21) / S;
            out[0] = (sm31 + sm13) / S;
            out[1] = (sm23 + sm32) / S;
            out[2] = 0.25 * S;
          }
  
          return out;
        };
      }();
      /**
       * Creates a matrix from a quaternion rotation, vector translation and vector scale
       * This is equivalent to (but much faster than):
       *
       *     mat4.identity(dest);
       *     mat4.translate(dest, vec);
       *     let quatMat = mat4.create();
       *     quat4.toMat4(quat, quatMat);
       *     mat4.multiply(dest, quatMat);
       *     mat4.scale(dest, scale)
       *
       * @param {mat4} out mat4 receiving operation result
       * @param {quat4} q Rotation quaternion
       * @param {vec3} v Translation vector
       * @param {vec3} s Scaling vector
       * @returns {mat4} out
       */
  
      function fromRotationTranslationScale(out, q, v, s) {
        // Quaternion math
        let x = q[0],
            y = q[1],
            z = q[2],
            w = q[3];
        let x2 = x + x;
        let y2 = y + y;
        let z2 = z + z;
        let xx = x * x2;
        let xy = x * y2;
        let xz = x * z2;
        let yy = y * y2;
        let yz = y * z2;
        let zz = z * z2;
        let wx = w * x2;
        let wy = w * y2;
        let wz = w * z2;
        let sx = s[0];
        let sy = s[1];
        let sz = s[2];
        out[0] = (1 - (yy + zz)) * sx;
        out[1] = (xy + wz) * sx;
        out[2] = (xz - wy) * sx;
        out[3] = 0;
        out[4] = (xy - wz) * sy;
        out[5] = (1 - (xx + zz)) * sy;
        out[6] = (yz + wx) * sy;
        out[7] = 0;
        out[8] = (xz + wy) * sz;
        out[9] = (yz - wx) * sz;
        out[10] = (1 - (xx + yy)) * sz;
        out[11] = 0;
        out[12] = v[0];
        out[13] = v[1];
        out[14] = v[2];
        out[15] = 1;
        return out;
      }
      /**
       * Calculates a 4x4 matrix from the given quaternion
       *
       * @param {mat4} out mat4 receiving operation result
       * @param {quat} q Quaternion to create matrix from
       *
       * @returns {mat4} out
       */
  
      function fromQuat(out, q) {
        let x = q[0],
            y = q[1],
            z = q[2],
            w = q[3];
        let x2 = x + x;
        let y2 = y + y;
        let z2 = z + z;
        let xx = x * x2;
        let yx = y * x2;
        let yy = y * y2;
        let zx = z * x2;
        let zy = z * y2;
        let zz = z * z2;
        let wx = w * x2;
        let wy = w * y2;
        let wz = w * z2;
        out[0] = 1 - yy - zz;
        out[1] = yx + wz;
        out[2] = zx - wy;
        out[3] = 0;
        out[4] = yx - wz;
        out[5] = 1 - xx - zz;
        out[6] = zy + wx;
        out[7] = 0;
        out[8] = zx + wy;
        out[9] = zy - wx;
        out[10] = 1 - xx - yy;
        out[11] = 0;
        out[12] = 0;
        out[13] = 0;
        out[14] = 0;
        out[15] = 1;
        return out;
      }
      /**
       * Generates a perspective projection matrix with the given bounds
       *
       * @param {mat4} out mat4 frustum matrix will be written into
       * @param {number} fovy Vertical field of view in radians
       * @param {number} aspect Aspect ratio. typically viewport width/height
       * @param {number} near Near bound of the frustum
       * @param {number} far Far bound of the frustum
       * @returns {mat4} out
       */
  
      function perspective(out, fovy, aspect, near, far) {
        let f = 1.0 / Math.tan(fovy / 2);
        let nf = 1 / (near - far);
        out[0] = f / aspect;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = 0;
        out[5] = f;
        out[6] = 0;
        out[7] = 0;
        out[8] = 0;
        out[9] = 0;
        out[10] = (far + near) * nf;
        out[11] = -1;
        out[12] = 0;
        out[13] = 0;
        out[14] = 2 * far * near * nf;
        out[15] = 0;
        return out;
      }
      /**
       * Generates a orthogonal projection matrix with the given bounds
       *
       * @param {mat4} out mat4 frustum matrix will be written into
       * @param {number} left Left bound of the frustum
       * @param {number} right Right bound of the frustum
       * @param {number} bottom Bottom bound of the frustum
       * @param {number} top Top bound of the frustum
       * @param {number} near Near bound of the frustum
       * @param {number} far Far bound of the frustum
       * @returns {mat4} out
       */
  
      function ortho(out, left, right, bottom, top, near, far) {
        let lr = 1 / (left - right);
        let bt = 1 / (bottom - top);
        let nf = 1 / (near - far);
        out[0] = -2 * lr;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = 0;
        out[5] = -2 * bt;
        out[6] = 0;
        out[7] = 0;
        out[8] = 0;
        out[9] = 0;
        out[10] = 2 * nf;
        out[11] = 0;
        out[12] = (left + right) * lr;
        out[13] = (top + bottom) * bt;
        out[14] = (far + near) * nf;
        out[15] = 1;
        return out;
      }
      /**
       * Generates a matrix that makes something look at something else.
       *
       * @param {mat4} out mat4 frustum matrix will be written into
       * @param {vec3} eye Position of the viewer
       * @param {vec3} target Point the viewer is looking at
       * @param {vec3} up vec3 pointing up
       * @returns {mat4} out
       */
  
      function targetTo(out, eye, target, up) {
        let eyex = eye[0],
            eyey = eye[1],
            eyez = eye[2],
            upx = up[0],
            upy = up[1],
            upz = up[2];
        let z0 = eyex - target[0],
            z1 = eyey - target[1],
            z2 = eyez - target[2];
        let len = z0 * z0 + z1 * z1 + z2 * z2;
  
        if (len === 0) {
          // eye and target are in the same position
          z2 = 1;
        } else {
          len = 1 / Math.sqrt(len);
          z0 *= len;
          z1 *= len;
          z2 *= len;
        }
  
        let x0 = upy * z2 - upz * z1,
            x1 = upz * z0 - upx * z2,
            x2 = upx * z1 - upy * z0;
        len = x0 * x0 + x1 * x1 + x2 * x2;
  
        if (len === 0) {
          // up and z are parallel
          if (upz) {
            upx += 1e-6;
          } else if (upy) {
            upz += 1e-6;
          } else {
            upy += 1e-6;
          }
  
          x0 = upy * z2 - upz * z1, x1 = upz * z0 - upx * z2, x2 = upx * z1 - upy * z0;
          len = x0 * x0 + x1 * x1 + x2 * x2;
        }
  
        len = 1 / Math.sqrt(len);
        x0 *= len;
        x1 *= len;
        x2 *= len;
        out[0] = x0;
        out[1] = x1;
        out[2] = x2;
        out[3] = 0;
        out[4] = z1 * x2 - z2 * x1;
        out[5] = z2 * x0 - z0 * x2;
        out[6] = z0 * x1 - z1 * x0;
        out[7] = 0;
        out[8] = z0;
        out[9] = z1;
        out[10] = z2;
        out[11] = 0;
        out[12] = eyex;
        out[13] = eyey;
        out[14] = eyez;
        out[15] = 1;
        return out;
      }
  
      class Mat4 extends Array {
        constructor(m00 = 1, m01 = 0, m02 = 0, m03 = 0, m10 = 0, m11 = 1, m12 = 0, m13 = 0, m20 = 0, m21 = 0, m22 = 1, m23 = 0, m30 = 0, m31 = 0, m32 = 0, m33 = 1) {
          super(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33);
          return this;
        }
  
        get x() {
          return this[12];
        }
  
        get y() {
          return this[13];
        }
  
        get z() {
          return this[14];
        }
  
        get w() {
          return this[15];
        }
  
        set x(v) {
          this[12] = v;
        }
  
        set y(v) {
          this[13] = v;
        }
  
        set z(v) {
          this[14] = v;
        }
  
        set w(v) {
          this[15] = v;
        }
  
        set(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
          if (m00.length) return this.copy(m00);
          set$1(this, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33);
          return this;
        }
  
        translate(v, m = this) {
          translate(this, m, v);
          return this;
        }
  
        rotate(v, axis, m = this) {
          rotate(this, m, v, axis);
          return this;
        }
  
        scale(v, m = this) {
          scale$1(this, m, typeof v === 'number' ? [v, v, v] : v);
          return this;
        }
  
        multiply(ma, mb) {
          if (mb) {
            multiply$1(this, ma, mb);
          } else {
            multiply$1(this, this, ma);
          }
  
          return this;
        }
  
        identity() {
          identity(this);
          return this;
        }
  
        copy(m) {
          copy$1(this, m);
          return this;
        }
  
        fromPerspective({
          fov,
          aspect,
          near,
          far
        } = {}) {
          perspective(this, fov, aspect, near, far);
          return this;
        }
  
        fromOrthogonal({
          left,
          right,
          bottom,
          top,
          near,
          far
        }) {
          ortho(this, left, right, bottom, top, near, far);
          return this;
        }
  
        fromQuaternion(q) {
          fromQuat(this, q);
          return this;
        }
  
        setPosition(v) {
          this.x = v[0];
          this.y = v[1];
          this.z = v[2];
          return this;
        }
  
        inverse(m = this) {
          invert(this, m);
          return this;
        }
  
        compose(q, pos, scale) {
          fromRotationTranslationScale(this, q, pos, scale);
          return this;
        }
  
        getRotation(q) {
          getRotation(q, this);
          return this;
        }
  
        getTranslation(pos) {
          getTranslation(pos, this);
          return this;
        }
  
        getScaling(scale) {
          getScaling(scale, this);
          return this;
        }
  
        getMaxScaleOnAxis() {
          return getMaxScaleOnAxis(this);
        }
  
        lookAt(eye, target, up) {
          targetTo(this, eye, target, up);
          return this;
        }
  
        determinant() {
          return determinant(this);
        }
  
        fromArray(a, o = 0) {
          this[0] = a[o];
          this[1] = a[o + 1];
          this[2] = a[o + 2];
          this[3] = a[o + 3];
          this[4] = a[o + 4];
          this[5] = a[o + 5];
          this[6] = a[o + 6];
          this[7] = a[o + 7];
          this[8] = a[o + 8];
          this[9] = a[o + 9];
          this[10] = a[o + 10];
          this[11] = a[o + 11];
          this[12] = a[o + 12];
          this[13] = a[o + 13];
          this[14] = a[o + 14];
          this[15] = a[o + 15];
          return this;
        }
  
        toArray(a = [], o = 0) {
          a[o] = this[0];
          a[o + 1] = this[1];
          a[o + 2] = this[2];
          a[o + 3] = this[3];
          a[o + 4] = this[4];
          a[o + 5] = this[5];
          a[o + 6] = this[6];
          a[o + 7] = this[7];
          a[o + 8] = this[8];
          a[o + 9] = this[9];
          a[o + 10] = this[10];
          a[o + 11] = this[11];
          a[o + 12] = this[12];
          a[o + 13] = this[13];
          a[o + 14] = this[14];
          a[o + 15] = this[15];
          return a;
        }
  
      }
  
      /**
       * Copy the values from one vec4 to another
       *
       * @param {vec4} out the receiving vector
       * @param {vec4} a the source vector
       * @returns {vec4} out
       */
  
      function copy$2(out, a) {
        out[0] = a[0];
        out[1] = a[1];
        out[2] = a[2];
        out[3] = a[3];
        return out;
      }
      /**
       * Set the components of a vec4 to the given values
       *
       * @param {vec4} out the receiving vector
       * @param {Number} x X component
       * @param {Number} y Y component
       * @param {Number} z Z component
       * @param {Number} w W component
       * @returns {vec4} out
       */
  
      function set$2(out, x, y, z, w) {
        out[0] = x;
        out[1] = y;
        out[2] = z;
        out[3] = w;
        return out;
      }
      /**
       * Normalize a vec4
       *
       * @param {vec4} out the receiving vector
       * @param {vec4} a vector to normalize
       * @returns {vec4} out
       */
  
      function normalize$1(out, a) {
        let x = a[0];
        let y = a[1];
        let z = a[2];
        let w = a[3];
        let len = x * x + y * y + z * z + w * w;
  
        if (len > 0) {
          len = 1 / Math.sqrt(len);
        }
  
        out[0] = x * len;
        out[1] = y * len;
        out[2] = z * len;
        out[3] = w * len;
        return out;
      }
      /**
       * Calculates the dot product of two vec4's
       *
       * @param {vec4} a the first operand
       * @param {vec4} b the second operand
       * @returns {Number} dot product of a and b
       */
  
      function dot$1(a, b) {
        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
      }
  
      /**
       * Set a quat to the identity quaternion
       *
       * @param {quat} out the receiving quaternion
       * @returns {quat} out
       */
  
      function identity$1(out) {
        out[0] = 0;
        out[1] = 0;
        out[2] = 0;
        out[3] = 1;
        return out;
      }
      /**
       * Sets a quat from the given angle and rotation axis,
       * then returns it.
       *
       * @param {quat} out the receiving quaternion
       * @param {vec3} axis the axis around which to rotate
       * @param {Number} rad the angle in radians
       * @returns {quat} out
       **/
  
      function setAxisAngle(out, axis, rad) {
        rad = rad * 0.5;
        let s = Math.sin(rad);
        out[0] = s * axis[0];
        out[1] = s * axis[1];
        out[2] = s * axis[2];
        out[3] = Math.cos(rad);
        return out;
      }
      /**
       * Multiplies two quats
       *
       * @param {quat} out the receiving quaternion
       * @param {quat} a the first operand
       * @param {quat} b the second operand
       * @returns {quat} out
       */
  
      function multiply$2(out, a, b) {
        let ax = a[0],
            ay = a[1],
            az = a[2],
            aw = a[3];
        let bx = b[0],
            by = b[1],
            bz = b[2],
            bw = b[3];
        out[0] = ax * bw + aw * bx + ay * bz - az * by;
        out[1] = ay * bw + aw * by + az * bx - ax * bz;
        out[2] = az * bw + aw * bz + ax * by - ay * bx;
        out[3] = aw * bw - ax * bx - ay * by - az * bz;
        return out;
      }
      /**
       * Rotates a quaternion by the given angle about the X axis
       *
       * @param {quat} out quat receiving operation result
       * @param {quat} a quat to rotate
       * @param {number} rad angle (in radians) to rotate
       * @returns {quat} out
       */
  
      function rotateX(out, a, rad) {
        rad *= 0.5;
        let ax = a[0],
            ay = a[1],
            az = a[2],
            aw = a[3];
        let bx = Math.sin(rad),
            bw = Math.cos(rad);
        out[0] = ax * bw + aw * bx;
        out[1] = ay * bw + az * bx;
        out[2] = az * bw - ay * bx;
        out[3] = aw * bw - ax * bx;
        return out;
      }
      /**
       * Rotates a quaternion by the given angle about the Y axis
       *
       * @param {quat} out quat receiving operation result
       * @param {quat} a quat to rotate
       * @param {number} rad angle (in radians) to rotate
       * @returns {quat} out
       */
  
      function rotateY(out, a, rad) {
        rad *= 0.5;
        let ax = a[0],
            ay = a[1],
            az = a[2],
            aw = a[3];
        let by = Math.sin(rad),
            bw = Math.cos(rad);
        out[0] = ax * bw - az * by;
        out[1] = ay * bw + aw * by;
        out[2] = az * bw + ax * by;
        out[3] = aw * bw - ay * by;
        return out;
      }
      /**
       * Rotates a quaternion by the given angle about the Z axis
       *
       * @param {quat} out quat receiving operation result
       * @param {quat} a quat to rotate
       * @param {number} rad angle (in radians) to rotate
       * @returns {quat} out
       */
  
      function rotateZ(out, a, rad) {
        rad *= 0.5;
        let ax = a[0],
            ay = a[1],
            az = a[2],
            aw = a[3];
        let bz = Math.sin(rad),
            bw = Math.cos(rad);
        out[0] = ax * bw + ay * bz;
        out[1] = ay * bw - ax * bz;
        out[2] = az * bw + aw * bz;
        out[3] = aw * bw - az * bz;
        return out;
      }
      /**
       * Performs a spherical linear interpolation between two quat
       *
       * @param {quat} out the receiving quaternion
       * @param {quat} a the first operand
       * @param {quat} b the second operand
       * @param {Number} t interpolation amount between the two inputs
       * @returns {quat} out
       */
  
      function slerp(out, a, b, t) {
        // benchmarks:
        //    http://jsperf.com/quaternion-slerp-implementations
        let ax = a[0],
            ay = a[1],
            az = a[2],
            aw = a[3];
        let bx = b[0],
            by = b[1],
            bz = b[2],
            bw = b[3];
        let omega, cosom, sinom, scale0, scale1; // calc cosine
  
        cosom = ax * bx + ay * by + az * bz + aw * bw; // adjust signs (if necessary)
  
        if (cosom < 0.0) {
          cosom = -cosom;
          bx = -bx;
          by = -by;
          bz = -bz;
          bw = -bw;
        } // calculate coefficients
  
  
        if (1.0 - cosom > 0.000001) {
          // standard case (slerp)
          omega = Math.acos(cosom);
          sinom = Math.sin(omega);
          scale0 = Math.sin((1.0 - t) * omega) / sinom;
          scale1 = Math.sin(t * omega) / sinom;
        } else {
          // "from" and "to" quaternions are very close
          //  ... so we can do a linear interpolation
          scale0 = 1.0 - t;
          scale1 = t;
        } // calculate final values
  
  
        out[0] = scale0 * ax + scale1 * bx;
        out[1] = scale0 * ay + scale1 * by;
        out[2] = scale0 * az + scale1 * bz;
        out[3] = scale0 * aw + scale1 * bw;
        return out;
      }
      /**
       * Calculates the inverse of a quat
       *
       * @param {quat} out the receiving quaternion
       * @param {quat} a quat to calculate inverse of
       * @returns {quat} out
       */
  
      function invert$1(out, a) {
        let a0 = a[0],
            a1 = a[1],
            a2 = a[2],
            a3 = a[3];
        let dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3;
        let invDot = dot ? 1.0 / dot : 0; // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0
  
        out[0] = -a0 * invDot;
        out[1] = -a1 * invDot;
        out[2] = -a2 * invDot;
        out[3] = a3 * invDot;
        return out;
      }
      /**
       * Calculates the conjugate of a quat
       * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result.
       *
       * @param {quat} out the receiving quaternion
       * @param {quat} a quat to calculate conjugate of
       * @returns {quat} out
       */
  
      function conjugate(out, a) {
        out[0] = -a[0];
        out[1] = -a[1];
        out[2] = -a[2];
        out[3] = a[3];
        return out;
      }
      /**
       * Creates a quaternion from the given 3x3 rotation matrix.
       *
       * NOTE: The resultant quaternion is not normalized, so you should be sure
       * to renormalize the quaternion yourself where necessary.
       *
       * @param {quat} out the receiving quaternion
       * @param {mat3} m rotation matrix
       * @returns {quat} out
       * @function
       */
  
      function fromMat3(out, m) {
        // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
        // article "Quaternion Calculus and Fast Animation".
        let fTrace = m[0] + m[4] + m[8];
        let fRoot;
  
        if (fTrace > 0.0) {
          // |w| > 1/2, may as well choose w > 1/2
          fRoot = Math.sqrt(fTrace + 1.0); // 2w
  
          out[3] = 0.5 * fRoot;
          fRoot = 0.5 / fRoot; // 1/(4w)
  
          out[0] = (m[5] - m[7]) * fRoot;
          out[1] = (m[6] - m[2]) * fRoot;
          out[2] = (m[1] - m[3]) * fRoot;
        } else {
          // |w| <= 1/2
          let i = 0;
          if (m[4] > m[0]) i = 1;
          if (m[8] > m[i * 3 + i]) i = 2;
          let j = (i + 1) % 3;
          let k = (i + 2) % 3;
          fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1.0);
          out[i] = 0.5 * fRoot;
          fRoot = 0.5 / fRoot;
          out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot;
          out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot;
          out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot;
        }
  
        return out;
      }
      /**
       * Creates a quaternion from the given euler angle x, y, z.
       *
       * @param {quat} out the receiving quaternion
       * @param {vec3} euler Angles to rotate around each axis in degrees.
       * @param {String} order detailing order of operations. Default 'XYZ'.
       * @returns {quat} out
       * @function
       */
  
      function fromEuler(out, euler, order = 'YXZ') {
        let sx = Math.sin(euler[0] * 0.5);
        let cx = Math.cos(euler[0] * 0.5);
        let sy = Math.sin(euler[1] * 0.5);
        let cy = Math.cos(euler[1] * 0.5);
        let sz = Math.sin(euler[2] * 0.5);
        let cz = Math.cos(euler[2] * 0.5);
  
        if (order === 'XYZ') {
          out[0] = sx * cy * cz + cx * sy * sz;
          out[1] = cx * sy * cz - sx * cy * sz;
          out[2] = cx * cy * sz + sx * sy * cz;
          out[3] = cx * cy * cz - sx * sy * sz;
        } else if (order === 'YXZ') {
          out[0] = sx * cy * cz + cx * sy * sz;
          out[1] = cx * sy * cz - sx * cy * sz;
          out[2] = cx * cy * sz - sx * sy * cz;
          out[3] = cx * cy * cz + sx * sy * sz;
        } else if (order === 'ZXY') {
          out[0] = sx * cy * cz - cx * sy * sz;
          out[1] = cx * sy * cz + sx * cy * sz;
          out[2] = cx * cy * sz + sx * sy * cz;
          out[3] = cx * cy * cz - sx * sy * sz;
        } else if (order === 'ZYX') {
          out[0] = sx * cy * cz - cx * sy * sz;
          out[1] = cx * sy * cz + sx * cy * sz;
          out[2] = cx * cy * sz - sx * sy * cz;
          out[3] = cx * cy * cz + sx * sy * sz;
        } else if (order === 'YZX') {
          out[0] = sx * cy * cz + cx * sy * sz;
          out[1] = cx * sy * cz + sx * cy * sz;
          out[2] = cx * cy * sz - sx * sy * cz;
          out[3] = cx * cy * cz - sx * sy * sz;
        } else if (order === 'XZY') {
          out[0] = sx * cy * cz - cx * sy * sz;
          out[1] = cx * sy * cz - sx * cy * sz;
          out[2] = cx * cy * sz + sx * sy * cz;
          out[3] = cx * cy * cz + sx * sy * sz;
        }
  
        return out;
      }
      /**
       * Copy the values from one quat to another
       *
       * @param {quat} out the receiving quaternion
       * @param {quat} a the source quaternion
       * @returns {quat} out
       * @function
       */
  
      const copy$3 = copy$2;
      /**
       * Set the components of a quat to the given values
       *
       * @param {quat} out the receiving quaternion
       * @param {Number} x X component
       * @param {Number} y Y component
       * @param {Number} z Z component
       * @param {Number} w W component
       * @returns {quat} out
       * @function
       */
  
      const set$3 = set$2;
      /**
       * Calculates the dot product of two quat's
       *
       * @param {quat} a the first operand
       * @param {quat} b the second operand
       * @returns {Number} dot product of a and b
       * @function
       */
  
      const dot$2 = dot$1;
      /**
       * Normalize a quat
       *
       * @param {quat} out the receiving quaternion
       * @param {quat} a quaternion to normalize
       * @returns {quat} out
       * @function
       */
  
      const normalize$2 = normalize$1;
  
      class Quat extends Array {
        constructor(x = 0, y = 0, z = 0, w = 1) {
          super(x, y, z, w);
          this.onChange = void 0;
  
          this.onChange = () => {};
  
          return this;
        }
  
        get x() {
          return this[0];
        }
  
        get y() {
          return this[1];
        }
  
        get z() {
          return this[2];
        }
  
        get w() {
          return this[3];
        }
  
        set x(v) {
          this[0] = v;
          this.onChange();
        }
  
        set y(v) {
          this[1] = v;
          this.onChange();
        }
  
        set z(v) {
          this[2] = v;
          this.onChange();
        }
  
        set w(v) {
          this[3] = v;
          this.onChange();
        }
  
        identity() {
          identity$1(this);
          this.onChange();
          return this;
        }
  
        set(x, y, z, w) {
          if (x.length) return this.copy(x);
          set$3(this, x, y, z, w);
          this.onChange();
          return this;
        }
  
        rotateX(a) {
          rotateX(this, this, a);
          this.onChange();
          return this;
        }
  
        rotateY(a) {
          rotateY(this, this, a);
          this.onChange();
          return this;
        }
  
        rotateZ(a) {
          rotateZ(this, this, a);
          this.onChange();
          return this;
        }
  
        inverse(q = this) {
          invert$1(this, q);
          this.onChange();
          return this;
        }
  
        conjugate(q = this) {
          conjugate(this, q);
          this.onChange();
          return this;
        }
  
        copy(q) {
          copy$3(this, q);
          this.onChange();
          return this;
        }
  
        normalize(q = this) {
          normalize$2(this, q);
          this.onChange();
          return this;
        }
  
        multiply(qA, qB) {
          if (qB) {
            multiply$2(this, qA, qB);
          } else {
            multiply$2(this, this, qA);
          }
  
          this.onChange();
          return this;
        }
  
        dot(v) {
          return dot$2(this, v);
        }
  
        fromMatrix3(matrix3) {
          fromMat3(this, matrix3);
          this.onChange();
          return this;
        }
  
        fromEuler(euler) {
          fromEuler(this, euler, euler.order);
          return this;
        }
  
        fromAxisAngle(axis, a) {
          setAxisAngle(this, axis, a);
          return this;
        }
  
        slerp(q, t) {
          slerp(this, this, q, t);
          return this;
        }
  
        fromArray(a, o = 0) {
          this[0] = a[o];
          this[1] = a[o + 1];
          this[2] = a[o + 2];
          this[3] = a[o + 3];
          return this;
        }
  
        toArray(a = [], o = 0) {
          a[o] = this[0];
          a[o + 1] = this[1];
          a[o + 2] = this[2];
          a[o + 3] = this[3];
          return a;
        }
  
      }
  
      // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
      function fromRotationMatrix(out, m, order = 'YXZ') {
        if (order === 'XYZ') {
          out[1] = Math.asin(Math.min(Math.max(m[8], -1), 1));
  
          if (Math.abs(m[8]) < 0.99999) {
            out[0] = Math.atan2(-m[9], m[10]);
            out[2] = Math.atan2(-m[4], m[0]);
          } else {
            out[0] = Math.atan2(m[6], m[5]);
            out[2] = 0;
          }
        } else if (order === 'YXZ') {
          out[0] = Math.asin(-Math.min(Math.max(m[9], -1), 1));
  
          if (Math.abs(m[9]) < 0.99999) {
            out[1] = Math.atan2(m[8], m[10]);
            out[2] = Math.atan2(m[1], m[5]);
          } else {
            out[1] = Math.atan2(-m[2], m[0]);
            out[2] = 0;
          }
        } else if (order === 'ZXY') {
          out[0] = Math.asin(Math.min(Math.max(m[6], -1), 1));
  
          if (Math.abs(m[6]) < 0.99999) {
            out[1] = Math.atan2(-m[2], m[10]);
            out[2] = Math.atan2(-m[4], m[5]);
          } else {
            out[1] = 0;
            out[2] = Math.atan2(m[1], m[0]);
          }
        } else if (order === 'ZYX') {
          out[1] = Math.asin(-Math.min(Math.max(m[2], -1), 1));
  
          if (Math.abs(m[2]) < 0.99999) {
            out[0] = Math.atan2(m[6], m[10]);
            out[2] = Math.atan2(m[1], m[0]);
          } else {
            out[0] = 0;
            out[2] = Math.atan2(-m[4], m[5]);
          }
        } else if (order === 'YZX') {
          out[2] = Math.asin(Math.min(Math.max(m[1], -1), 1));
  
          if (Math.abs(m[1]) < 0.99999) {
            out[0] = Math.atan2(-m[9], m[5]);
            out[1] = Math.atan2(-m[2], m[0]);
          } else {
            out[0] = 0;
            out[1] = Math.atan2(m[8], m[10]);
          }
        } else if (order === 'XZY') {
          out[2] = Math.asin(-Math.min(Math.max(m[4], -1), 1));
  
          if (Math.abs(m[4]) < 0.99999) {
            out[0] = Math.atan2(m[6], m[5]);
            out[1] = Math.atan2(m[8], m[0]);
          } else {
            out[0] = Math.atan2(-m[9], m[10]);
            out[1] = 0;
          }
        }
  
        return out;
      }
  
      const tmpMat4 = new Mat4();
      class Euler extends Array {
        constructor(x = 0, y = x, z = x, order = 'YXZ') {
          super(x, y, z);
          this.onChange = void 0;
          this.order = void 0;
          this.order = order;
  
          this.onChange = () => {};
  
          return this;
        }
  
        get x() {
          return this[0];
        }
  
        get y() {
          return this[1];
        }
  
        get z() {
          return this[2];
        }
  
        set x(v) {
          this[0] = v;
          this.onChange();
        }
  
        set y(v) {
          this[1] = v;
          this.onChange();
        }
  
        set z(v) {
          this[2] = v;
          this.onChange();
        }
  
        set(x, y = x, z = x) {
          if (x.length) return this.copy(x);
          this[0] = x;
          this[1] = y;
          this[2] = z;
          this.onChange();
          return this;
        }
  
        copy(v) {
          this[0] = v[0];
          this[1] = v[1];
          this[2] = v[2];
          this.onChange();
          return this;
        }
  
        reorder(order) {
          this.order = order;
          this.onChange();
          return this;
        }
  
        fromRotationMatrix(m, order = this.order) {
          fromRotationMatrix(this, m, order);
          return this;
        }
  
        fromQuaternion(q, order = this.order) {
          tmpMat4.fromQuaternion(q);
          return this.fromRotationMatrix(tmpMat4, order);
        }
  
      }
  
      class Transform {
        constructor() {
          this.parent = void 0;
          this.children = void 0;
          this.visible = void 0;
          this.matrix = void 0;
          this.worldMatrix = void 0;
          this.matrixAutoUpdate = void 0;
          this.worldMatrixNeedsUpdate = void 0;
          this.position = void 0;
          this.scale = void 0;
          this.up = void 0;
          this.quaternion = void 0;
          this.rotation = void 0;
          this.parent = null;
          this.children = [];
          this.visible = true;
          this.matrix = new Mat4();
          this.worldMatrix = new Mat4();
          this.matrixAutoUpdate = true;
          this.up = new Vec3(0, 1, 0);
          this.position = new Vec3();
          this.scale = new Vec3(1);
          this.quaternion = new Quat();
          this.rotation = new Euler();
  
          this.rotation.onChange = () => this.quaternion.fromEuler(this.rotation);
  
          this.quaternion.onChange = () => this.rotation.fromQuaternion(this.quaternion);
        }
  
        setParent(parent, notifyParent = true) {
          if (notifyParent && this.parent && parent !== this.parent) this.parent.removeChild(this, false);
          this.parent = parent;
          if (notifyParent && parent) parent.addChild(this, false);
        }
  
        addChild(child, notifyChild = true) {
          if (!~this.children.indexOf(child)) this.children.push(child);
          if (notifyChild) child.setParent(this, false);
        }
  
        removeChild(child, notifyChild = true) {
          if (!!~this.children.indexOf(child)) this.children.splice(this.children.indexOf(child), 1);
          if (notifyChild) child.setParent(null, false);
        }
  
        updateMatrixWorld(force) {
          if (this.matrixAutoUpdate) this.updateMatrix();
  
          if (this.worldMatrixNeedsUpdate || force) {
            if (this.parent === null) this.worldMatrix.copy(this.matrix);else this.worldMatrix.multiply(this.parent.worldMatrix, this.matrix);
            this.worldMatrixNeedsUpdate = false;
            force = true;
          }
  
          for (let i = 0, l = this.children.length; i < l; i++) {
            this.children[i].updateMatrixWorld(force);
          }
        }
  
        updateMatrix() {
          this.matrix.compose(this.quaternion, this.position, this.scale);
          this.worldMatrixNeedsUpdate = true;
        }
  
        traverse(callback) {
          // Return true in callback to stop traversing children
          if (callback(this)) return;
  
          for (let i = 0, l = this.children.length; i < l; i++) {
            this.children[i].traverse(callback);
          }
        }
  
        decompose() {
          this.matrix.getTranslation(this.position);
          this.matrix.getRotation(this.quaternion);
          this.matrix.getScaling(this.scale);
          this.rotation.fromQuaternion(this.quaternion);
        }
  
        lookAt(target, invert = false) {
          if (invert) this.matrix.lookAt(this.position, target, this.up);else this.matrix.lookAt(target, this.position, this.up);
          this.matrix.getRotation(this.quaternion);
          this.rotation.fromQuaternion(this.quaternion);
        }
  
      }
  
      const tempMat4 = new Mat4();
      const tempVec3a = new Vec3();
      const tempVec3b = new Vec3();
      class Camera extends Transform {
        constructor(gl, {
          near = 0.1,
          far = 100,
          fov = 45,
          aspect = 1,
          left,
          right,
          bottom,
          top,
          zoom = 1
        } = {}) {
          super();
          this.near = void 0;
          this.far = void 0;
          this.fov = void 0;
          this.aspect = void 0;
          this.left = void 0;
          this.right = void 0;
          this.bottom = void 0;
          this.top = void 0;
          this.zoom = void 0;
          this.projectionMatrix = void 0;
          this.viewMatrix = void 0;
          this.projectionViewMatrix = void 0;
          this.worldPosition = void 0;
          this.type = void 0;
          this.frustum = void 0;
          Object.assign(this, {
            near,
            far,
            fov,
            aspect,
            left,
            right,
            bottom,
            top,
            zoom
          });
          this.projectionMatrix = new Mat4();
          this.viewMatrix = new Mat4();
          this.projectionViewMatrix = new Mat4();
          this.worldPosition = new Vec3(); // Use orthographic if left/right set, else default to perspective camera
  
          this.type = left || right ? 'orthographic' : 'perspective';
          if (this.type === 'orthographic') this.orthographic();else this.perspective();
        }
  
        perspective({
          near = this.near,
          far = this.far,
          fov = this.fov,
          aspect = this.aspect
        } = {}) {
          Object.assign(this, {
            near,
            far,
            fov,
            aspect
          });
          this.projectionMatrix.fromPerspective({
            fov: fov * (Math.PI / 180),
            aspect,
            near,
            far
          });
          this.type = 'perspective';
          return this;
        }
  
        orthographic({
          near = this.near,
          far = this.far,
          left = this.left,
          right = this.right,
          bottom = this.bottom,
          top = this.top,
          zoom = this.zoom
        } = {}) {
          Object.assign(this, {
            near,
            far,
            left,
            right,
            bottom,
            top,
            zoom
          });
          left /= zoom;
          right /= zoom;
          bottom /= zoom;
          top /= zoom;
          this.projectionMatrix.fromOrthogonal({
            left,
            right,
            bottom,
            top,
            near,
            far
          });
          this.type = 'orthographic';
          return this;
        }
  
        updateMatrixWorld() {
          super.updateMatrixWorld();
          this.viewMatrix.inverse(this.worldMatrix);
          this.worldMatrix.getTranslation(this.worldPosition); // used for sorting
  
          this.projectionViewMatrix.multiply(this.projectionMatrix, this.viewMatrix);
          return this;
        }
  
        lookAt(target) {
          super.lookAt(target, true);
          return this;
        } // Project 3D coordinate to 2D point
  
  
        project(v) {
          v.applyMatrix4(this.viewMatrix);
          v.applyMatrix4(this.projectionMatrix);
          return this;
        } // Unproject 2D point to 3D coordinate
  
  
        unproject(v) {
          v.applyMatrix4(tempMat4.inverse(this.projectionMatrix));
          v.applyMatrix4(this.worldMatrix);
          return this;
        }
  
        updateFrustum() {
          if (!this.frustum) {
            this.frustum = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()];
          }
  
          const m = this.projectionViewMatrix;
          this.frustum[0].set(m[3] - m[0], m[7] - m[4], m[11] - m[8]).constant = m[15] - m[12]; // -x
  
          this.frustum[1].set(m[3] + m[0], m[7] + m[4], m[11] + m[8]).constant = m[15] + m[12]; // +x
  
          this.frustum[2].set(m[3] + m[1], m[7] + m[5], m[11] + m[9]).constant = m[15] + m[13]; // +y
  
          this.frustum[3].set(m[3] - m[1], m[7] - m[5], m[11] - m[9]).constant = m[15] - m[13]; // -y
  
          this.frustum[4].set(m[3] - m[2], m[7] - m[6], m[11] - m[10]).constant = m[15] - m[14]; // +z (far)
  
          this.frustum[5].set(m[3] + m[2], m[7] + m[6], m[11] + m[10]).constant = m[15] + m[14]; // -z (near)
  
          for (let i = 0; i < 6; i++) {
            const invLen = 1.0 / this.frustum[i].distance();
            this.frustum[i].multiply(invLen);
            this.frustum[i].constant *= invLen;
          }
        }
  
        frustumIntersectsMesh(node) {
          // If no position attribute, treat as frustumCulled false
          if (!node.geometry.attributes.position) return true;
          if (!node.geometry.bounds || node.geometry.bounds.radius === Infinity) node.geometry.computeBoundingSphere();
          if (!node.geometry.bounds) return true;
          const center = tempVec3a;
          center.copy(node.geometry.bounds.center);
          center.applyMatrix4(node.worldMatrix);
          const radius = node.geometry.bounds.radius * node.worldMatrix.getMaxScaleOnAxis();
          return this.frustumIntersectsSphere(center, radius);
        }
  
        frustumIntersectsSphere(center, radius) {
          const normal = tempVec3b;
  
          for (let i = 0; i < 6; i++) {
            const plane = this.frustum[i];
            const distance = normal.copy(plane).dot(center) + plane.constant;
            if (distance < -radius) return false;
          }
  
          return true;
        }
  
      }
  
      /**
       * Copies the upper-left 3x3 values into the given mat3.
       *
       * @param {mat3} out the receiving 3x3 matrix
       * @param {mat4} a   the source 4x4 matrix
       * @returns {mat3} out
       */
  
      function fromMat4(out, a) {
        out[0] = a[0];
        out[1] = a[1];
        out[2] = a[2];
        out[3] = a[4];
        out[4] = a[5];
        out[5] = a[6];
        out[6] = a[8];
        out[7] = a[9];
        out[8] = a[10];
        return out;
      }
      /**
       * Calculates a 3x3 matrix from the given quaternion
       *
       * @param {mat3} out mat3 receiving operation result
       * @param {quat} q Quaternion to create matrix from
       *
       * @returns {mat3} out
       */
  
      function fromQuat$1(out, q) {
        let x = q[0],
            y = q[1],
            z = q[2],
            w = q[3];
        let x2 = x + x;
        let y2 = y + y;
        let z2 = z + z;
        let xx = x * x2;
        let yx = y * x2;
        let yy = y * y2;
        let zx = z * x2;
        let zy = z * y2;
        let zz = z * z2;
        let wx = w * x2;
        let wy = w * y2;
        let wz = w * z2;
        out[0] = 1 - yy - zz;
        out[3] = yx - wz;
        out[6] = zx + wy;
        out[1] = yx + wz;
        out[4] = 1 - xx - zz;
        out[7] = zy - wx;
        out[2] = zx - wy;
        out[5] = zy + wx;
        out[8] = 1 - xx - yy;
        return out;
      }
      /**
       * Copy the values from one mat3 to another
       *
       * @param {mat3} out the receiving matrix
       * @param {mat3} a the source matrix
       * @returns {mat3} out
       */
  
      function copy$4(out, a) {
        out[0] = a[0];
        out[1] = a[1];
        out[2] = a[2];
        out[3] = a[3];
        out[4] = a[4];
        out[5] = a[5];
        out[6] = a[6];
        out[7] = a[7];
        out[8] = a[8];
        return out;
      }
      /**
       * Set the components of a mat3 to the given values
       *
       * @param {mat3} out the receiving matrix
       * @returns {mat3} out
       */
  
      function set$4(out, m00, m01, m02, m10, m11, m12, m20, m21, m22) {
        out[0] = m00;
        out[1] = m01;
        out[2] = m02;
        out[3] = m10;
        out[4] = m11;
        out[5] = m12;
        out[6] = m20;
        out[7] = m21;
        out[8] = m22;
        return out;
      }
      /**
       * Set a mat3 to the identity matrix
       *
       * @param {mat3} out the receiving matrix
       * @returns {mat3} out
       */
  
      function identity$2(out) {
        out[0] = 1;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = 1;
        out[5] = 0;
        out[6] = 0;
        out[7] = 0;
        out[8] = 1;
        return out;
      }
      /**
       * Inverts a mat3
       *
       * @param {mat3} out the receiving matrix
       * @param {mat3} a the source matrix
       * @returns {mat3} out
       */
  
      function invert$2(out, a) {
        let a00 = a[0],
            a01 = a[1],
            a02 = a[2];
        let a10 = a[3],
            a11 = a[4],
            a12 = a[5];
        let a20 = a[6],
            a21 = a[7],
            a22 = a[8];
        let b01 = a22 * a11 - a12 * a21;
        let b11 = -a22 * a10 + a12 * a20;
        let b21 = a21 * a10 - a11 * a20; // Calculate the determinant
  
        let det = a00 * b01 + a01 * b11 + a02 * b21;
  
        if (!det) {
          return null;
        }
  
        det = 1.0 / det;
        out[0] = b01 * det;
        out[1] = (-a22 * a01 + a02 * a21) * det;
        out[2] = (a12 * a01 - a02 * a11) * det;
        out[3] = b11 * det;
        out[4] = (a22 * a00 - a02 * a20) * det;
        out[5] = (-a12 * a00 + a02 * a10) * det;
        out[6] = b21 * det;
        out[7] = (-a21 * a00 + a01 * a20) * det;
        out[8] = (a11 * a00 - a01 * a10) * det;
        return out;
      }
      /**
       * Multiplies two mat3's
       *
       * @param {mat3} out the receiving matrix
       * @param {mat3} a the first operand
       * @param {mat3} b the second operand
       * @returns {mat3} out
       */
  
      function multiply$3(out, a, b) {
        let a00 = a[0],
            a01 = a[1],
            a02 = a[2];
        let a10 = a[3],
            a11 = a[4],
            a12 = a[5];
        let a20 = a[6],
            a21 = a[7],
            a22 = a[8];
        let b00 = b[0],
            b01 = b[1],
            b02 = b[2];
        let b10 = b[3],
            b11 = b[4],
            b12 = b[5];
        let b20 = b[6],
            b21 = b[7],
            b22 = b[8];
        out[0] = b00 * a00 + b01 * a10 + b02 * a20;
        out[1] = b00 * a01 + b01 * a11 + b02 * a21;
        out[2] = b00 * a02 + b01 * a12 + b02 * a22;
        out[3] = b10 * a00 + b11 * a10 + b12 * a20;
        out[4] = b10 * a01 + b11 * a11 + b12 * a21;
        out[5] = b10 * a02 + b11 * a12 + b12 * a22;
        out[6] = b20 * a00 + b21 * a10 + b22 * a20;
        out[7] = b20 * a01 + b21 * a11 + b22 * a21;
        out[8] = b20 * a02 + b21 * a12 + b22 * a22;
        return out;
      }
      /**
       * Translate a mat3 by the given vector
       *
       * @param {mat3} out the receiving matrix
       * @param {mat3} a the matrix to translate
       * @param {vec2} v vector to translate by
       * @returns {mat3} out
       */
  
      function translate$1(out, a, v) {
        let a00 = a[0],
            a01 = a[1],
            a02 = a[2],
            a10 = a[3],
            a11 = a[4],
            a12 = a[5],
            a20 = a[6],
            a21 = a[7],
            a22 = a[8],
            x = v[0],
            y = v[1];
        out[0] = a00;
        out[1] = a01;
        out[2] = a02;
        out[3] = a10;
        out[4] = a11;
        out[5] = a12;
        out[6] = x * a00 + y * a10 + a20;
        out[7] = x * a01 + y * a11 + a21;
        out[8] = x * a02 + y * a12 + a22;
        return out;
      }
      /**
       * Rotates a mat3 by the given angle
       *
       * @param {mat3} out the receiving matrix
       * @param {mat3} a the matrix to rotate
       * @param {Number} rad the angle to rotate the matrix by
       * @returns {mat3} out
       */
  
      function rotate$1(out, a, rad) {
        let a00 = a[0],
            a01 = a[1],
            a02 = a[2],
            a10 = a[3],
            a11 = a[4],
            a12 = a[5],
            a20 = a[6],
            a21 = a[7],
            a22 = a[8],
            s = Math.sin(rad),
            c = Math.cos(rad);
        out[0] = c * a00 + s * a10;
        out[1] = c * a01 + s * a11;
        out[2] = c * a02 + s * a12;
        out[3] = c * a10 - s * a00;
        out[4] = c * a11 - s * a01;
        out[5] = c * a12 - s * a02;
        out[6] = a20;
        out[7] = a21;
        out[8] = a22;
        return out;
      }
      /**
       * Scales the mat3 by the dimensions in the given vec2
       *
       * @param {mat3} out the receiving matrix
       * @param {mat3} a the matrix to rotate
       * @param {vec2} v the vec2 to scale the matrix by
       * @returns {mat3} out
       **/
  
      function scale$2(out, a, v) {
        let x = v[0],
            y = v[1];
        out[0] = x * a[0];
        out[1] = x * a[1];
        out[2] = x * a[2];
        out[3] = y * a[3];
        out[4] = y * a[4];
        out[5] = y * a[5];
        out[6] = a[6];
        out[7] = a[7];
        out[8] = a[8];
        return out;
      }
      /**
       * Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix
       *
       * @param {mat3} out mat3 receiving operation result
       * @param {mat4} a Mat4 to derive the normal matrix from
       *
       * @returns {mat3} out
       */
  
      function normalFromMat4(out, a) {
        let a00 = a[0],
            a01 = a[1],
            a02 = a[2],
            a03 = a[3];
        let a10 = a[4],
            a11 = a[5],
            a12 = a[6],
            a13 = a[7];
        let a20 = a[8],
            a21 = a[9],
            a22 = a[10],
            a23 = a[11];
        let a30 = a[12],
            a31 = a[13],
            a32 = a[14],
            a33 = a[15];
        let b00 = a00 * a11 - a01 * a10;
        let b01 = a00 * a12 - a02 * a10;
        let b02 = a00 * a13 - a03 * a10;
        let b03 = a01 * a12 - a02 * a11;
        let b04 = a01 * a13 - a03 * a11;
        let b05 = a02 * a13 - a03 * a12;
        let b06 = a20 * a31 - a21 * a30;
        let b07 = a20 * a32 - a22 * a30;
        let b08 = a20 * a33 - a23 * a30;
        let b09 = a21 * a32 - a22 * a31;
        let b10 = a21 * a33 - a23 * a31;
        let b11 = a22 * a33 - a23 * a32; // Calculate the determinant
  
        let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
  
        if (!det) {
          return null;
        }
  
        det = 1.0 / det;
        out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
        out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
        out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
        out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
        out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
        out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
        out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
        out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
        out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
        return out;
      }
  
      class Mat3 extends Array {
        constructor(m00 = 1, m01 = 0, m02 = 0, m10 = 0, m11 = 1, m12 = 0, m20 = 0, m21 = 0, m22 = 1) {
          super(m00, m01, m02, m10, m11, m12, m20, m21, m22);
          return this;
        }
  
        set(m00, m01, m02, m10, m11, m12, m20, m21, m22) {
          if (m00.length) return this.copy(m00);
          set$4(this, m00, m01, m02, m10, m11, m12, m20, m21, m22);
          return this;
        }
  
        translate(v, m = this) {
          translate$1(this, m, v);
          return this;
        }
  
        rotate(v, m = this) {
          rotate$1(this, m, v);
          return this;
        }
  
        scale(v, m = this) {
          scale$2(this, m, v);
          return this;
        }
  
        multiply(ma, mb) {
          if (mb) {
            multiply$3(this, ma, mb);
          } else {
            multiply$3(this, this, ma);
          }
  
          return this;
        }
  
        identity() {
          identity$2(this);
          return this;
        }
  
        copy(m) {
          copy$4(this, m);
          return this;
        }
  
        fromMatrix4(m) {
          fromMat4(this, m);
          return this;
        }
  
        fromQuaternion(q) {
          fromQuat$1(this, q);
          return this;
        }
  
        fromBasis(vec3a, vec3b, vec3c) {
          this.set(vec3a[0], vec3a[1], vec3a[2], vec3b[0], vec3b[1], vec3b[2], vec3c[0], vec3c[1], vec3c[2]);
          return this;
        }
  
        inverse(m = this) {
          invert$2(this, m);
          return this;
        }
  
        getNormalMatrix(m) {
          normalFromMat4(this, m);
          return this;
        }
  
      }
  
      let ID$3 = 0;
      class Mesh extends Transform {
        // raycast.ts
        constructor(gl, {
          geometry,
          program,
          mode = gl.TRIANGLES,
          frustumCulled = true,
          renderOrder = 0
        } = {}) {
          super();
          this.name = void 0;
          this.numInstances = void 0;
          this.gl = void 0;
          this.id = void 0;
          this.geometry = void 0;
          this.program = void 0;
          this.mode = void 0;
          this.frustumCulled = void 0;
          this.renderOrder = void 0;
          this.modelViewMatrix = void 0;
          this.normalMatrix = void 0;
          this.beforeRenderCallbacks = void 0;
          this.afterRenderCallbacks = void 0;
          this.hit = null;
          if (!gl.canvas) console.error('gl not passed as first argument to Mesh');
          this.gl = gl;
          this.id = ID$3++;
          this.geometry = geometry;
          this.program = program;
          this.mode = mode; // Used to skip frustum culling
  
          this.frustumCulled = frustumCulled; // Override sorting to force an order
  
          this.renderOrder = renderOrder;
          this.modelViewMatrix = new Mat4();
          this.normalMatrix = new Mat3();
          this.beforeRenderCallbacks = [];
          this.afterRenderCallbacks = [];
        }
  
        onBeforeRender(f) {
          this.beforeRenderCallbacks.push(f);
          return this;
        }
  
        onAfterRender(f) {
          this.afterRenderCallbacks.push(f);
          return this;
        }
  
        draw({
          camera
        } = {}) {
          this.beforeRenderCallbacks.forEach(f => f && f({
            mesh: this,
            camera
          })); // Set the matrix uniforms
  
          if (camera) {
            // Add empty matrix uniforms to program if unset
            if (!this.program.uniforms.modelMatrix) {
              Object.assign(this.program.uniforms, {
                modelMatrix: {
                  value: null
                },
                viewMatrix: {
                  value: null
                },
                modelViewMatrix: {
                  value: null
                },
                normalMatrix: {
                  value: null
                },
                projectionMatrix: {
                  value: null
                },
                cameraPosition: {
                  value: null
                }
              });
            } // Set the matrix uniforms
  
  
            this.program.uniforms.projectionMatrix.value = camera.projectionMatrix;
            this.program.uniforms.cameraPosition.value = camera.worldPosition;
            this.program.uniforms.viewMatrix.value = camera.viewMatrix;
            this.modelViewMatrix.multiply(camera.viewMatrix, this.worldMatrix);
            this.normalMatrix.getNormalMatrix(this.modelViewMatrix);
            this.program.uniforms.modelMatrix.value = this.worldMatrix;
            this.program.uniforms.modelViewMatrix.value = this.modelViewMatrix;
            this.program.uniforms.normalMatrix.value = this.normalMatrix;
          } // determine if faces need to be flipped - when mesh scaled negatively
  
  
          let flipFaces = this.program.cullFace && this.worldMatrix.determinant() < 0;
          this.program.use({
            flipFaces
          });
          this.geometry.draw({
            mode: this.mode,
            program: this.program
          });
          this.afterRenderCallbacks.forEach(f => f && f({
            mesh: this,
            camera
          }));
        }
  
      }
  
      /**
       * Copy the values from one vec2 to another
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the source vector
       * @returns {vec2} out
       */
  
      function copy$5(out, a) {
        out[0] = a[0];
        out[1] = a[1];
        return out;
      }
      /**
       * Set the components of a vec2 to the given values
       *
       * @param {vec2} out the receiving vector
       * @param {Number} x X component
       * @param {Number} y Y component
       * @returns {vec2} out
       */
  
      function set$5(out, x, y) {
        out[0] = x;
        out[1] = y;
        return out;
      }
      /**
       * Adds two vec2's
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @returns {vec2} out
       */
  
      function add$1(out, a, b) {
        out[0] = a[0] + b[0];
        out[1] = a[1] + b[1];
        return out;
      }
      /**
       * Subtracts vector b from vector a
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @returns {vec2} out
       */
  
      function subtract$1(out, a, b) {
        out[0] = a[0] - b[0];
        out[1] = a[1] - b[1];
        return out;
      }
      /**
       * Multiplies two vec2's
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @returns {vec2} out
       */
  
      function multiply$4(out, a, b) {
        out[0] = a[0] * b[0];
        out[1] = a[1] * b[1];
        return out;
      }
      /**
       * Divides two vec2's
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @returns {vec2} out
       */
  
      function divide$1(out, a, b) {
        out[0] = a[0] / b[0];
        out[1] = a[1] / b[1];
        return out;
      }
      /**
       * Scales a vec2 by a scalar number
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the vector to scale
       * @param {Number} b amount to scale the vector by
       * @returns {vec2} out
       */
  
      function scale$3(out, a, b) {
        out[0] = a[0] * b;
        out[1] = a[1] * b;
        return out;
      }
      /**
       * Calculates the euclidian distance between two vec2's
       *
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @returns {Number} distance between a and b
       */
  
      function distance$1(a, b) {
        var x = b[0] - a[0],
            y = b[1] - a[1];
        return Math.sqrt(x * x + y * y);
      }
      /**
       * Calculates the squared euclidian distance between two vec2's
       *
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @returns {Number} squared distance between a and b
       */
  
      function squaredDistance$1(a, b) {
        var x = b[0] - a[0],
            y = b[1] - a[1];
        return x * x + y * y;
      }
      /**
       * Calculates the length of a vec2
       *
       * @param {vec2} a vector to calculate length of
       * @returns {Number} length of a
       */
  
      function length$1(a) {
        var x = a[0],
            y = a[1];
        return Math.sqrt(x * x + y * y);
      }
      /**
       * Calculates the squared length of a vec2
       *
       * @param {vec2} a vector to calculate squared length of
       * @returns {Number} squared length of a
       */
  
      function squaredLength$1(a) {
        var x = a[0],
            y = a[1];
        return x * x + y * y;
      }
      /**
       * Negates the components of a vec2
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a vector to negate
       * @returns {vec2} out
       */
  
      function negate$1(out, a) {
        out[0] = -a[0];
        out[1] = -a[1];
        return out;
      }
      /**
       * Returns the inverse of the components of a vec2
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a vector to invert
       * @returns {vec2} out
       */
  
      function inverse$1(out, a) {
        out[0] = 1.0 / a[0];
        out[1] = 1.0 / a[1];
        return out;
      }
      /**
       * Normalize a vec2
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a vector to normalize
       * @returns {vec2} out
       */
  
      function normalize$3(out, a) {
        var x = a[0],
            y = a[1];
        var len = x * x + y * y;
  
        if (len > 0) {
          //TODO: evaluate use of glm_invsqrt here?
          len = 1 / Math.sqrt(len);
        }
  
        out[0] = a[0] * len;
        out[1] = a[1] * len;
        return out;
      }
      /**
       * Calculates the dot product of two vec2's
       *
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @returns {Number} dot product of a and b
       */
  
      function dot$3(a, b) {
        return a[0] * b[0] + a[1] * b[1];
      }
      /**
       * Computes the cross product of two vec2's
       * Note that the cross product returns a scalar
       *
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @returns {Number} cross product of a and b
       */
  
      function cross$1(a, b) {
        return a[0] * b[1] - a[1] * b[0];
      }
      /**
       * Performs a linear interpolation between two vec2's
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the first operand
       * @param {vec2} b the second operand
       * @param {Number} t interpolation amount between the two inputs
       * @returns {vec2} out
       */
  
      function lerp$1(out, a, b, t) {
        var ax = a[0],
            ay = a[1];
        out[0] = ax + t * (b[0] - ax);
        out[1] = ay + t * (b[1] - ay);
        return out;
      }
      /**
       * Transforms the vec2 with a mat3
       * 3rd vector component is implicitly '1'
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the vector to transform
       * @param {mat3} m matrix to transform with
       * @returns {vec2} out
       */
  
      function transformMat3(out, a, m) {
        var x = a[0],
            y = a[1];
        out[0] = m[0] * x + m[3] * y + m[6];
        out[1] = m[1] * x + m[4] * y + m[7];
        return out;
      }
      /**
       * Transforms the vec2 with a mat4
       * 3rd vector component is implicitly '0'
       * 4th vector component is implicitly '1'
       *
       * @param {vec2} out the receiving vector
       * @param {vec2} a the vector to transform
       * @param {mat4} m matrix to transform with
       * @returns {vec2} out
       */
  
      function transformMat4$1(out, a, m) {
        let x = a[0];
        let y = a[1];
        out[0] = m[0] * x + m[4] * y + m[12];
        out[1] = m[1] * x + m[5] * y + m[13];
        return out;
      }
      /**
       * Returns whether or not the vectors exactly have the same elements in the same position (when compared with ===)
       *
       * @param {vec2} a The first vector.
       * @param {vec2} b The second vector.
       * @returns {Boolean} True if the vectors are equal, false otherwise.
       */
  
      function exactEquals$1(a, b) {
        return a[0] === b[0] && a[1] === b[1];
      }
  
      class Vec2 extends Array {
        constructor(x = 0, y = x) {
          super(x, y);
          return this;
        }
  
        get x() {
          return this[0];
        }
  
        get y() {
          return this[1];
        }
  
        set x(v) {
          this[0] = v;
        }
  
        set y(v) {
          this[1] = v;
        }
  
        set(x, y = x) {
          if (x.length) return this.copy(x);
          set$5(this, x, y);
          return this;
        }
  
        copy(v) {
          copy$5(this, v);
          return this;
        }
  
        add(va, vb) {
          if (vb) add$1(this, va, vb);else add$1(this, this, va);
          return this;
        }
  
        sub(va, vb) {
          if (vb) subtract$1(this, va, vb);else subtract$1(this, this, va);
          return this;
        }
  
        multiply(v) {
          if (v.length) multiply$4(this, this, v);else scale$3(this, this, v);
          return this;
        }
  
        divide(v) {
          if (v.length) divide$1(this, this, v);else scale$3(this, this, 1 / v);
          return this;
        }
  
        inverse(v = this) {
          inverse$1(this, v);
          return this;
        } // Can't use 'length' as Array.prototype uses it
  
  
        len() {
          return length$1(this);
        }
  
        distance(v) {
          if (v) return distance$1(this, v);else return length$1(this);
        }
  
        squaredLen() {
          return this.squaredDistance();
        }
  
        squaredDistance(v) {
          if (v) return squaredDistance$1(this, v);else return squaredLength$1(this);
        }
  
        negate(v = this) {
          negate$1(this, v);
          return this;
        }
  
        cross(va, vb) {
          if (vb) return cross$1(va, vb);
          return cross$1(this, va);
        }
  
        scale(v) {
          scale$3(this, this, v);
          return this;
        }
  
        normalize() {
          normalize$3(this, this);
          return this;
        }
  
        dot(v) {
          return dot$3(this, v);
        }
  
        equals(v) {
          return exactEquals$1(this, v);
        }
  
        applyMatrix3(mat3) {
          transformMat3(this, this, mat3);
          return this;
        }
  
        applyMatrix4(mat4) {
          transformMat4$1(this, this, mat4);
          return this;
        }
  
        lerp(v, a) {
          lerp$1(this, this, v, a);
        }
  
        clone() {
          return new Vec2(this[0], this[1]);
        }
  
        fromArray(a, o = 0) {
          this[0] = a[o];
          this[1] = a[o + 1];
          return this;
        }
  
        toArray(a = [], o = 0) {
          a[o] = this[0];
          a[o + 1] = this[1];
          return a;
        }
  
      }
  
      class Plane extends Geometry {
        constructor(gl, {
          width = 1,
          height = 1,
          widthSegments = 1,
          heightSegments = 1,
          attributes = {}
        } = {}) {
          const wSegs = widthSegments;
          const hSegs = heightSegments; // Determine length of arrays
  
          const num = (wSegs + 1) * (hSegs + 1);
          const numIndices = wSegs * hSegs * 6; // Generate empty arrays once
  
          const position = new Float32Array(num * 3);
          const normal = new Float32Array(num * 3);
          const uv = new Float32Array(num * 2);
          const index = num > 65536 ? new Uint32Array(numIndices) : new Uint16Array(numIndices);
          Plane.buildPlane(position, normal, uv, index, width, height, 0, wSegs, hSegs);
          Object.assign(attributes, {
            position: {
              size: 3,
              data: position
            },
            normal: {
              size: 3,
              data: normal
            },
            uv: {
              size: 2,
              data: uv
            },
            index: {
              data: index
            }
          });
          super(gl, attributes);
        }
  
        static buildPlane(position, normal, uv, index, width, height, depth, wSegs, hSegs, u = 0, v = 1, w = 2, uDir = 1, vDir = -1, i = 0, ii = 0) {
          const io = i;
          const segW = width / wSegs;
          const segH = height / hSegs;
  
          for (let iy = 0; iy <= hSegs; iy++) {
            let y = iy * segH - height / 2;
  
            for (let ix = 0; ix <= wSegs; ix++, i++) {
              let x = ix * segW - width / 2;
              position[i * 3 + u] = x * uDir;
              position[i * 3 + v] = y * vDir;
              position[i * 3 + w] = depth / 2;
              normal[i * 3 + u] = 0;
              normal[i * 3 + v] = 0;
              normal[i * 3 + w] = depth >= 0 ? 1 : -1;
              uv[i * 2] = ix / wSegs;
              uv[i * 2 + 1] = 1 - iy / hSegs;
              if (iy === hSegs || ix === wSegs) continue;
              let a = io + ix + iy * (wSegs + 1);
              let b = io + ix + (iy + 1) * (wSegs + 1);
              let c = io + ix + (iy + 1) * (wSegs + 1) + 1;
              let d = io + ix + iy * (wSegs + 1) + 1;
              index[ii * 6] = a;
              index[ii * 6 + 1] = b;
              index[ii * 6 + 2] = d;
              index[ii * 6 + 3] = b;
              index[ii * 6 + 4] = c;
              index[ii * 6 + 5] = d;
              ii++;
            }
          }
        }
  
      }
  
      class Box extends Geometry {
        constructor(gl, {
          width = 1,
          height = 1,
          depth = 1,
          widthSegments = 1,
          heightSegments = 1,
          depthSegments = 1,
          attributes = {}
        } = {}) {
          const wSegs = widthSegments;
          const hSegs = heightSegments;
          const dSegs = depthSegments;
          const num = (wSegs + 1) * (hSegs + 1) * 2 + (wSegs + 1) * (dSegs + 1) * 2 + (hSegs + 1) * (dSegs + 1) * 2;
          const numIndices = (wSegs * hSegs * 2 + wSegs * dSegs * 2 + hSegs * dSegs * 2) * 6;
          const position = new Float32Array(num * 3);
          const normal = new Float32Array(num * 3);
          const uv = new Float32Array(num * 2);
          const index = num > 65536 ? new Uint32Array(numIndices) : new Uint16Array(numIndices);
          let i = 0;
          let ii = 0; // left, right
  
          Plane.buildPlane(position, normal, uv, index, depth, height, width, dSegs, hSegs, 2, 1, 0, -1, -1, i, ii);
          Plane.buildPlane(position, normal, uv, index, depth, height, -width, dSegs, hSegs, 2, 1, 0, 1, -1, i += (dSegs + 1) * (hSegs + 1), ii += dSegs * hSegs); // top, bottom
  
          Plane.buildPlane(position, normal, uv, index, width, depth, height, dSegs, hSegs, 0, 2, 1, 1, 1, i += (dSegs + 1) * (hSegs + 1), ii += dSegs * hSegs);
          Plane.buildPlane(position, normal, uv, index, width, depth, -height, dSegs, hSegs, 0, 2, 1, 1, -1, i += (wSegs + 1) * (dSegs + 1), ii += wSegs * dSegs); // front, back
  
          Plane.buildPlane(position, normal, uv, index, width, height, -depth, wSegs, hSegs, 0, 1, 2, -1, -1, i += (wSegs + 1) * (dSegs + 1), ii += wSegs * dSegs);
          Plane.buildPlane(position, normal, uv, index, width, height, depth, wSegs, hSegs, 0, 1, 2, 1, -1, i += (wSegs + 1) * (hSegs + 1), ii += wSegs * hSegs);
          Object.assign(attributes, {
            position: {
              size: 3,
              data: position
            },
            normal: {
              size: 3,
              data: normal
            },
            uv: {
              size: 2,
              data: uv
            },
            index: {
              data: index
            }
          });
          super(gl, attributes);
        }
  
      }
  
      class Sphere extends Geometry {
        constructor(gl, {
          radius = 0.5,
          widthSegments = 16,
          heightSegments = Math.ceil(widthSegments * 0.5),
          phiStart = 0,
          phiLength = Math.PI * 2,
          thetaStart = 0,
          thetaLength = Math.PI,
          attributes = {}
        } = {}) {
          const wSegs = widthSegments;
          const hSegs = heightSegments;
          const pStart = phiStart;
          const pLength = phiLength;
          const tStart = thetaStart;
          const tLength = thetaLength;
          const num = (wSegs + 1) * (hSegs + 1);
          const numIndices = wSegs * hSegs * 6;
          const position = new Float32Array(num * 3);
          const normal = new Float32Array(num * 3);
          const uv = new Float32Array(num * 2);
          const index = num > 65536 ? new Uint32Array(numIndices) : new Uint16Array(numIndices);
          let i = 0;
          let iv = 0;
          let ii = 0;
          let te = tStart + tLength;
          const grid = [];
          let n = new Vec3();
  
          for (let iy = 0; iy <= hSegs; iy++) {
            let vRow = [];
            let v = iy / hSegs;
  
            for (let ix = 0; ix <= wSegs; ix++, i++) {
              let u = ix / wSegs;
              let x = -radius * Math.cos(pStart + u * pLength) * Math.sin(tStart + v * tLength);
              let y = radius * Math.cos(tStart + v * tLength);
              let z = radius * Math.sin(pStart + u * pLength) * Math.sin(tStart + v * tLength);
              position[i * 3] = x;
              position[i * 3 + 1] = y;
              position[i * 3 + 2] = z;
              n.set(x, y, z).normalize();
              normal[i * 3] = n.x;
              normal[i * 3 + 1] = n.y;
              normal[i * 3 + 2] = n.z;
              uv[i * 2] = u;
              uv[i * 2 + 1] = 1 - v;
              vRow.push(iv++);
            }
  
            grid.push(vRow);
          }
  
          for (let iy = 0; iy < hSegs; iy++) {
            for (let ix = 0; ix < wSegs; ix++) {
              let a = grid[iy][ix + 1];
              let b = grid[iy][ix];
              let c = grid[iy + 1][ix];
              let d = grid[iy + 1][ix + 1];
  
              if (iy !== 0 || tStart > 0) {
                index[ii * 3] = a;
                index[ii * 3 + 1] = b;
                index[ii * 3 + 2] = d;
                ii++;
              }
  
              if (iy !== hSegs - 1 || te < Math.PI) {
                index[ii * 3] = b;
                index[ii * 3 + 1] = c;
                index[ii * 3 + 2] = d;
                ii++;
              }
            }
          }
  
          Object.assign(attributes, {
            position: {
              size: 3,
              data: position
            },
            normal: {
              size: 3,
              data: normal
            },
            uv: {
              size: 2,
              data: uv
            },
            index: {
              data: index
            }
          });
          super(gl, attributes);
        }
  
      }
  
      class Cylinder extends Geometry {
        constructor(gl, {
          radiusTop = 0.5,
          radiusBottom = 0.5,
          height = 1,
          radialSegments = 8,
          heightSegments = 1,
          openEnded = false,
          thetaStart = 0,
          thetaLength = Math.PI * 2,
          attributes = {}
        } = {}) {
          const rSegs = radialSegments;
          const hSegs = heightSegments;
          const tStart = thetaStart;
          const tLength = thetaLength;
          const numCaps = openEnded ? 0 : radiusBottom && radiusTop ? 2 : 1;
          const num = (rSegs + 1) * (hSegs + 1 + numCaps) + numCaps;
          const numIndices = rSegs * hSegs * 6 + numCaps * rSegs * 3;
          const position = new Float32Array(num * 3);
          const normal = new Float32Array(num * 3);
          const uv = new Float32Array(num * 2);
          const index = num > 65536 ? new Uint32Array(numIndices) : new Uint16Array(numIndices);
          let i = 0;
          let ii = 0;
          const indexArray = [];
          addHeight();
  
          if (!openEnded) {
            if (radiusTop) addCap(true);
            if (radiusBottom) addCap(false);
          }
  
          function addHeight() {
            let x, y;
            const n = new Vec3();
            const slope = (radiusBottom - radiusTop) / height;
  
            for (y = 0; y <= hSegs; y++) {
              const indexRow = [];
              const v = y / hSegs;
              const r = v * (radiusBottom - radiusTop) + radiusTop;
  
              for (x = 0; x <= rSegs; x++) {
                const u = x / rSegs;
                const theta = u * tLength + tStart;
                const sinTheta = Math.sin(theta);
                const cosTheta = Math.cos(theta);
                position.set([r * sinTheta, (0.5 - v) * height, r * cosTheta], i * 3);
                n.set(sinTheta, slope, cosTheta).normalize();
                normal.set([n.x, n.y, n.z], i * 3);
                uv.set([u, 1 - v], i * 2);
                indexRow.push(i++);
              }
  
              indexArray.push(indexRow);
            }
  
            for (x = 0; x < rSegs; x++) {
              for (y = 0; y < hSegs; y++) {
                const a = indexArray[y][x];
                const b = indexArray[y + 1][x];
                const c = indexArray[y + 1][x + 1];
                const d = indexArray[y][x + 1];
                index.set([a, b, d, b, c, d], ii * 3);
                ii += 2;
              }
            }
          }
  
          function addCap(isTop) {
            let x;
            const r = isTop === true ? radiusTop : radiusBottom;
            const sign = isTop === true ? 1 : -1;
            const centerIndex = i;
            position.set([0, 0.5 * height * sign, 0], i * 3);
            normal.set([0, sign, 0], i * 3);
            uv.set([0.5, 0.5], i * 2);
            i++;
  
            for (x = 0; x <= rSegs; x++) {
              const u = x / rSegs;
              const theta = u * tLength + tStart;
              const cosTheta = Math.cos(theta);
              const sinTheta = Math.sin(theta);
              position.set([r * sinTheta, 0.5 * height * sign, r * cosTheta], i * 3);
              normal.set([0, sign, 0], i * 3);
              uv.set([cosTheta * 0.5 + 0.5, sinTheta * 0.5 * sign + 0.5], i * 2);
              i++;
            }
  
            for (x = 0; x < rSegs; x++) {
              const j = centerIndex + x + 1;
  
              if (isTop) {
                index.set([j, j + 1, centerIndex], ii * 3);
              } else {
                index.set([j + 1, j, centerIndex], ii * 3);
              }
  
              ii++;
            }
          }
  
          Object.assign(attributes, {
            position: {
              size: 3,
              data: position
            },
            normal: {
              size: 3,
              data: normal
            },
            uv: {
              size: 2,
              data: uv
            },
            index: {
              data: index
            }
          });
          super(gl, attributes);
        }
  
      }
  
      // Based from ThreeJS' OrbitControls class, rewritten using es6 with some additions and subtractions.
      const STATE = {
        NONE: -1,
        ROTATE: 0,
        DOLLY: 1,
        PAN: 2,
        DOLLY_PAN: 3
      };
      const tempVec3$2 = new Vec3();
      const tempVec2a = new Vec2();
      const tempVec2b = new Vec2();
      function Orbit(object, // TODO: fov property only be used in pan()
      {
        element = document.body,
        enabled = true,
        target = new Vec3(),
        ease = 0.25,
        inertia = 0.85,
        enableRotate = true,
        rotateSpeed = 0.1,
        autoRotate = false,
        autoRotateSpeed = 1.0,
        enableZoom = true,
        zoomSpeed = 1,
        enablePan = true,
        panSpeed = 0.1,
        minPolarAngle = 0,
        maxPolarAngle = Math.PI,
        minAzimuthAngle = -Infinity,
        maxAzimuthAngle = Infinity,
        minDistance = 0,
        maxDistance = Infinity
      } = {}) {
        this.enabled = enabled;
        this.target = target; // Catch attempts to disable - set to 1 so has no effect
  
        ease = ease || 1;
        inertia = inertia || 0;
        this.minDistance = minDistance;
        this.maxDistance = maxDistance; // current position in sphericalTarget coordinates
  
        const sphericalDelta = {
          radius: 1,
          phi: 0,
          theta: 0
        };
        const sphericalTarget = {
          radius: 1,
          phi: 0,
          theta: 0
        };
        const spherical = {
          radius: 1,
          phi: 0,
          theta: 0
        };
        const panDelta = new Vec3(); // Grab initial position values
  
        const offset = new Vec3();
        offset.copy(object.position).sub(this.target);
        spherical.radius = sphericalTarget.radius = offset.distance();
        spherical.theta = sphericalTarget.theta = Math.atan2(offset.x, offset.z);
        spherical.phi = sphericalTarget.phi = Math.acos(Math.min(Math.max(offset.y / sphericalTarget.radius, -1), 1));
        this.offset = offset;
  
        this.update = () => {
          if (autoRotate) {
            handleAutoRotate();
          } // apply delta
  
  
          sphericalTarget.radius *= sphericalDelta.radius;
          sphericalTarget.theta += sphericalDelta.theta;
          sphericalTarget.phi += sphericalDelta.phi; // apply boundaries
  
          sphericalTarget.theta = Math.max(minAzimuthAngle, Math.min(maxAzimuthAngle, sphericalTarget.theta));
          sphericalTarget.phi = Math.max(minPolarAngle, Math.min(maxPolarAngle, sphericalTarget.phi));
          sphericalTarget.radius = Math.max(this.minDistance, Math.min(this.maxDistance, sphericalTarget.radius)); // ease values
  
          spherical.phi += (sphericalTarget.phi - spherical.phi) * ease;
          spherical.theta += (sphericalTarget.theta - spherical.theta) * ease;
          spherical.radius += (sphericalTarget.radius - spherical.radius) * ease; // apply pan to target. As offset is relative to target, it also shifts
  
          this.target.add(panDelta); // apply rotation to offset
  
          let sinPhiRadius = spherical.radius * Math.sin(Math.max(0.000001, spherical.phi));
          offset.x = sinPhiRadius * Math.sin(spherical.theta);
          offset.y = spherical.radius * Math.cos(spherical.phi);
          offset.z = sinPhiRadius * Math.cos(spherical.theta); // Apply updated values to object
  
          object.position.copy(this.target).add(offset);
          object.lookAt(this.target); // Apply inertia to values
  
          sphericalDelta.theta *= inertia;
          sphericalDelta.phi *= inertia;
          panDelta.multiply(inertia); // Reset scale every frame to avoid applying scale multiple times
  
          sphericalDelta.radius = 1;
        }; // Updates internals with new position
  
  
        this.forcePosition = () => {
          offset.copy(object.position).sub(this.target);
          spherical.radius = sphericalTarget.radius = offset.distance();
          spherical.theta = sphericalTarget.theta = Math.atan2(offset.x, offset.z);
          spherical.phi = sphericalTarget.phi = Math.acos(Math.min(Math.max(offset.y / sphericalTarget.radius, -1), 1));
          object.lookAt(this.target);
        }; // Everything below here just updates panDelta and sphericalDelta
        // Using those two objects' values, the orbit is calculated
  
  
        const rotateStart = new Vec2();
        const panStart = new Vec2();
        const dollyStart = new Vec2();
        let state = STATE.NONE;
        this.mouseButtons = {
          ORBIT: 0,
          ZOOM: 1,
          PAN: 2
        };
  
        function getZoomScale() {
          return Math.pow(0.95, zoomSpeed);
        }
  
        function panLeft(distance, m) {
          tempVec3$2.set(m[0], m[1], m[2]);
          tempVec3$2.multiply(-distance);
          panDelta.add(tempVec3$2);
        }
  
        function panUp(distance, m) {
          tempVec3$2.set(m[4], m[5], m[6]);
          tempVec3$2.multiply(distance);
          panDelta.add(tempVec3$2);
        }
  
        const pan = (deltaX, deltaY) => {
          // let el = element === document ? document.body : element;
          tempVec3$2.copy(object.position).sub(this.target);
          let targetDistance = tempVec3$2.distance();
          targetDistance *= Math.tan((object.fov || 45) / 2 * Math.PI / 180.0);
          panLeft(2 * deltaX * targetDistance / element.clientHeight, object.matrix);
          panUp(2 * deltaY * targetDistance / element.clientHeight, object.matrix);
        };
  
        function dolly(dollyScale) {
          sphericalDelta.radius /= dollyScale;
        }
  
        function handleAutoRotate() {
          const angle = 2 * Math.PI / 60 / 60 * autoRotateSpeed;
          sphericalDelta.theta -= angle;
        }
  
        function handleMoveRotate(x, y) {
          tempVec2a.set(x, y);
          tempVec2b.sub(tempVec2a, rotateStart).multiply(rotateSpeed); // let el = element === document ? document.body : element;
  
          sphericalDelta.theta -= 2 * Math.PI * tempVec2b.x / element.clientHeight;
          sphericalDelta.phi -= 2 * Math.PI * tempVec2b.y / element.clientHeight;
          rotateStart.copy(tempVec2a);
        }
  
        function handleMouseMoveDolly(e) {
          tempVec2a.set(e.clientX, e.clientY);
          tempVec2b.sub(tempVec2a, dollyStart);
  
          if (tempVec2b.y > 0) {
            dolly(getZoomScale());
          } else if (tempVec2b.y < 0) {
            dolly(1 / getZoomScale());
          }
  
          dollyStart.copy(tempVec2a);
        }
  
        function handleMovePan(x, y) {
          tempVec2a.set(x, y);
          tempVec2b.sub(tempVec2a, panStart).multiply(panSpeed);
          pan(tempVec2b.x, tempVec2b.y);
          panStart.copy(tempVec2a);
        }
  
        function handleTouchStartDollyPan(e) {
          if (enableZoom) {
            let dx = e.touches[0].pageX - e.touches[1].pageX;
            let dy = e.touches[0].pageY - e.touches[1].pageY;
            let distance = Math.sqrt(dx * dx + dy * dy);
            dollyStart.set(0, distance);
          }
  
          if (enablePan) {
            let x = 0.5 * (e.touches[0].pageX + e.touches[1].pageX);
            let y = 0.5 * (e.touches[0].pageY + e.touches[1].pageY);
            panStart.set(x, y);
          }
        }
  
        function handleTouchMoveDollyPan(e) {
          if (enableZoom) {
            let dx = e.touches[0].pageX - e.touches[1].pageX;
            let dy = e.touches[0].pageY - e.touches[1].pageY;
            let distance = Math.sqrt(dx * dx + dy * dy);
            tempVec2a.set(0, distance);
            tempVec2b.set(0, Math.pow(tempVec2a.y / dollyStart.y, zoomSpeed));
            dolly(tempVec2b.y);
            dollyStart.copy(tempVec2a);
          }
  
          if (enablePan) {
            let x = 0.5 * (e.touches[0].pageX + e.touches[1].pageX);
            let y = 0.5 * (e.touches[0].pageY + e.touches[1].pageY);
            handleMovePan(x, y);
          }
        }
  
        const onMouseDown = e => {
          if (!this.enabled) return;
  
          switch (e.button) {
            case this.mouseButtons.ORBIT:
              if (enableRotate === false) return;
              rotateStart.set(e.clientX, e.clientY);
              state = STATE.ROTATE;
              break;
  
            case this.mouseButtons.ZOOM:
              if (enableZoom === false) return;
              dollyStart.set(e.clientX, e.clientY);
              state = STATE.DOLLY;
              break;
  
            case this.mouseButtons.PAN:
              if (enablePan === false) return;
              panStart.set(e.clientX, e.clientY);
              state = STATE.PAN;
              break;
          }
  
          if (state !== STATE.NONE) {
            window.addEventListener('mousemove', onMouseMove, false);
            window.addEventListener('mouseup', onMouseUp, false);
          }
        };
  
        const onMouseMove = e => {
          if (!this.enabled) return;
  
          switch (state) {
            case STATE.ROTATE:
              if (enableRotate === false) return;
              handleMoveRotate(e.clientX, e.clientY);
              break;
  
            case STATE.DOLLY:
              if (enableZoom === false) return;
              handleMouseMoveDolly(e);
              break;
  
            case STATE.PAN:
              if (enablePan === false) return;
              handleMovePan(e.clientX, e.clientY);
              break;
          }
        };
  
        const onMouseUp = () => {
          window.removeEventListener('mousemove', onMouseMove, false);
          window.removeEventListener('mouseup', onMouseUp, false);
          state = STATE.NONE;
        };
  
        const onMouseWheel = e => {
          if (!this.enabled || !enableZoom || state !== STATE.NONE && state !== STATE.ROTATE) return;
          e.stopPropagation();
          e.preventDefault();
  
          if (e.deltaY < 0) {
            dolly(1 / getZoomScale());
          } else if (e.deltaY > 0) {
            dolly(getZoomScale());
          }
        };
  
        const onTouchStart = e => {
          if (!this.enabled) return;
          e.preventDefault();
  
          switch (e.touches.length) {
            case 1:
              if (enableRotate === false) return;
              rotateStart.set(e.touches[0].pageX, e.touches[0].pageY);
              state = STATE.ROTATE;
              break;
  
            case 2:
              if (enableZoom === false && enablePan === false) return;
              handleTouchStartDollyPan(e);
              state = STATE.DOLLY_PAN;
              break;
  
            default:
              state = STATE.NONE;
          }
        };
  
        const onTouchMove = e => {
          if (!this.enabled) return;
          e.preventDefault();
          e.stopPropagation();
  
          switch (e.touches.length) {
            case 1:
              if (enableRotate === false) return;
              handleMoveRotate(e.touches[0].pageX, e.touches[0].pageY);
              break;
  
            case 2:
              if (enableZoom === false && enablePan === false) return;
              handleTouchMoveDollyPan(e);
              break;
  
            default:
              state = STATE.NONE;
          }
        };
  
        const onTouchEnd = () => {
          if (!this.enabled) return;
          state = STATE.NONE;
        };
  
        const onContextMenu = e => {
          if (!this.enabled) return;
          e.preventDefault();
        };
  
        function addHandlers() {
          element.addEventListener('contextmenu', onContextMenu, false);
          element.addEventListener('mousedown', onMouseDown, false);
          element.addEventListener('wheel', onMouseWheel, {
            passive: false
          });
          element.addEventListener('touchstart', onTouchStart, {
            passive: false
          });
          element.addEventListener('touchend', onTouchEnd, false);
          element.addEventListener('touchmove', onTouchMove, {
            passive: false
          });
        }
  
        this.remove = function () {
          element.removeEventListener('contextmenu', onContextMenu);
          element.removeEventListener('mousedown', onMouseDown);
          element.removeEventListener('wheel', onMouseWheel);
          element.removeEventListener('touchstart', onTouchStart);
          element.removeEventListener('touchend', onTouchEnd);
          element.removeEventListener('touchmove', onTouchMove);
          window.removeEventListener('mousemove', onMouseMove);
          window.removeEventListener('mouseup', onMouseUp);
        };
  
        addHandlers();
      }
  
      const vertex =
      /* glsl */
  `
              precision highp float;
              precision highp int;
  
              attribute vec3 position;
              attribute vec3 normal;
  
              uniform mat4 modelViewMatrix;
              uniform mat4 projectionMatrix;
              uniform mat3 normalMatrix;
  
              varying vec3 vNormal;
  
              void main() {
                  vNormal = normalize(normalMatrix * normal);
                  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
              }
          `    ;
      const fragment =
      /* glsl */
  `
              precision highp float;
              precision highp int;
  
              varying vec3 vNormal;
  
              void main() {
                  vec3 normal = normalize(vNormal);
                  float lighting = dot(normal, normalize(vec3(-0.3, 0.8, 0.6)));
                  gl_FragColor.rgb = vec3(0.2, 0.8, 1.0) + lighting * 0.1;
                  gl_FragColor.a = 1.0;
              }
          `    ;
      const renderer = new Renderer({
        dpr: 2
      });
      const gl = renderer.gl;
      document.body.appendChild(gl.canvas);
      gl.clearColor(1, 1, 1, 1);
      const camera = new Camera(gl, {
        fov: 35
      });
      camera.position.set(0, 1, 7);
      camera.lookAt([0, 0, 0]);
      const controls = new Orbit(camera);
  
      function resize() {
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.perspective({
          aspect: gl.canvas.width / gl.canvas.height
        });
      }
  
      window.addEventListener('resize', resize, false);
      resize();
      const scene = new Transform();
      const planeGeometry = new Plane(gl);
      const sphereGeometry = new Sphere(gl);
      const cubeGeometry = new Box(gl);
      const cylinderGeometry = new Cylinder(gl);
      const program = new Program(gl, {
        vertex,
        fragment,
        // Don't cull faces so that plane is double sided - default is gl.BACK
        cullFace: null
      });
      const plane = new Mesh(gl, {
        geometry: planeGeometry,
        program
      });
      plane.position.set(0, 1.3, 0);
      plane.setParent(scene);
      const sphere = new Mesh(gl, {
        geometry: sphereGeometry,
        program
      });
      sphere.position.set(1.3, 0, 0);
      sphere.setParent(scene);
      const cube = new Mesh(gl, {
        geometry: cubeGeometry,
        program
      });
      cube.position.set(0, -1.3, 0);
      cube.setParent(scene);
      const cylinder = new Mesh(gl, {
        geometry: cylinderGeometry,
        program
      });
      cylinder.position.set(-1.3, 0, 0);
      cylinder.setParent(scene);
      requestAnimationFrame(update);
  
      function update() {
        requestAnimationFrame(update);
        controls.update();
        plane.rotation.y -= 0.02;
        sphere.rotation.y -= 0.03;
        cube.rotation.y -= 0.04;
        cylinder.rotation.y -= 0.02;
        renderer.render({
          scene,
          camera
        });
      }
  
      document.getElementsByClassName('Info')[0].innerHTML = 'Base Primitives - Plane, Cube, Sphere, Cylinder';
      document.title = 'OGL • Base Primitives - Plane, Cube, Sphere, Cylinder';
  
  }());
  