UNPKG

starchild-widget

Version:

Starchild Widget

1 lines 211 kB
{"version":3,"file":"vendor-ogl-BIUE0HLK.mjs","sources":["../node_modules/ogl/src/math/functions/Vec3Func.js","../node_modules/ogl/src/math/Vec3.js","../node_modules/ogl/src/core/Geometry.js","../node_modules/ogl/src/core/Program.js","../node_modules/ogl/src/core/Renderer.js","../node_modules/ogl/src/math/functions/Vec4Func.js","../node_modules/ogl/src/math/functions/QuatFunc.js","../node_modules/ogl/src/math/Quat.js","../node_modules/ogl/src/math/functions/Mat4Func.js","../node_modules/ogl/src/math/Mat4.js","../node_modules/ogl/src/math/functions/EulerFunc.js","../node_modules/ogl/src/math/Euler.js","../node_modules/ogl/src/core/Transform.js","../node_modules/ogl/src/math/functions/Mat3Func.js","../node_modules/ogl/src/math/Mat3.js","../node_modules/ogl/src/core/Mesh.js","../node_modules/ogl/src/math/functions/ColorFunc.js","../node_modules/ogl/src/math/Color.js","../node_modules/ogl/src/extras/Triangle.js"],"sourcesContent":["const EPSILON = 0.000001;\n\n/**\n * Calculates the length of a vec3\n *\n * @param {vec3} a vector to calculate length of\n * @returns {Number} length of a\n */\nexport function length(a) {\n let x = a[0];\n let y = a[1];\n let z = a[2];\n return Math.sqrt(x * x + y * y + z * z);\n}\n\n/**\n * Copy the values from one vec3 to another\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the source vector\n * @returns {vec3} out\n */\nexport function copy(out, a) {\n out[0] = a[0];\n out[1] = a[1];\n out[2] = a[2];\n return out;\n}\n\n/**\n * Set the components of a vec3 to the given values\n *\n * @param {vec3} out the receiving vector\n * @param {Number} x X component\n * @param {Number} y Y component\n * @param {Number} z Z component\n * @returns {vec3} out\n */\nexport function set(out, x, y, z) {\n out[0] = x;\n out[1] = y;\n out[2] = z;\n return out;\n}\n\n/**\n * Adds two vec3's\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @returns {vec3} out\n */\nexport function add(out, a, b) {\n out[0] = a[0] + b[0];\n out[1] = a[1] + b[1];\n out[2] = a[2] + b[2];\n return out;\n}\n\n/**\n * Subtracts vector b from vector a\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @returns {vec3} out\n */\nexport function subtract(out, a, b) {\n out[0] = a[0] - b[0];\n out[1] = a[1] - b[1];\n out[2] = a[2] - b[2];\n return out;\n}\n\n/**\n * Multiplies two vec3's\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @returns {vec3} out\n */\nexport function multiply(out, a, b) {\n out[0] = a[0] * b[0];\n out[1] = a[1] * b[1];\n out[2] = a[2] * b[2];\n return out;\n}\n\n/**\n * Divides two vec3's\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @returns {vec3} out\n */\nexport function divide(out, a, b) {\n out[0] = a[0] / b[0];\n out[1] = a[1] / b[1];\n out[2] = a[2] / b[2];\n return out;\n}\n\n/**\n * Scales a vec3 by a scalar number\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the vector to scale\n * @param {Number} b amount to scale the vector by\n * @returns {vec3} out\n */\nexport function scale(out, a, b) {\n out[0] = a[0] * b;\n out[1] = a[1] * b;\n out[2] = a[2] * b;\n return out;\n}\n\n/**\n * Calculates the euclidian distance between two vec3's\n *\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @returns {Number} distance between a and b\n */\nexport function distance(a, b) {\n let x = b[0] - a[0];\n let y = b[1] - a[1];\n let z = b[2] - a[2];\n return Math.sqrt(x * x + y * y + z * z);\n}\n\n/**\n * Calculates the squared euclidian distance between two vec3's\n *\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @returns {Number} squared distance between a and b\n */\nexport function squaredDistance(a, b) {\n let x = b[0] - a[0];\n let y = b[1] - a[1];\n let z = b[2] - a[2];\n return x * x + y * y + z * z;\n}\n\n/**\n * Calculates the squared length of a vec3\n *\n * @param {vec3} a vector to calculate squared length of\n * @returns {Number} squared length of a\n */\nexport function squaredLength(a) {\n let x = a[0];\n let y = a[1];\n let z = a[2];\n return x * x + y * y + z * z;\n}\n\n/**\n * Negates the components of a vec3\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a vector to negate\n * @returns {vec3} out\n */\nexport function negate(out, a) {\n out[0] = -a[0];\n out[1] = -a[1];\n out[2] = -a[2];\n return out;\n}\n\n/**\n * Returns the inverse of the components of a vec3\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a vector to invert\n * @returns {vec3} out\n */\nexport function inverse(out, a) {\n out[0] = 1.0 / a[0];\n out[1] = 1.0 / a[1];\n out[2] = 1.0 / a[2];\n return out;\n}\n\n/**\n * Normalize a vec3\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a vector to normalize\n * @returns {vec3} out\n */\nexport function normalize(out, a) {\n let x = a[0];\n let y = a[1];\n let z = a[2];\n let len = x * x + y * y + z * z;\n if (len > 0) {\n //TODO: evaluate use of glm_invsqrt here?\n len = 1 / Math.sqrt(len);\n }\n out[0] = a[0] * len;\n out[1] = a[1] * len;\n out[2] = a[2] * len;\n return out;\n}\n\n/**\n * Calculates the dot product of two vec3's\n *\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @returns {Number} dot product of a and b\n */\nexport function dot(a, b) {\n return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n}\n\n/**\n * Computes the cross product of two vec3's\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @returns {vec3} out\n */\nexport function cross(out, a, b) {\n let ax = a[0],\n ay = a[1],\n az = a[2];\n let bx = b[0],\n by = b[1],\n bz = b[2];\n\n out[0] = ay * bz - az * by;\n out[1] = az * bx - ax * bz;\n out[2] = ax * by - ay * bx;\n return out;\n}\n\n/**\n * Performs a linear interpolation between two vec3's\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @param {Number} t interpolation amount between the two inputs\n * @returns {vec3} out\n */\nexport function lerp(out, a, b, t) {\n let ax = a[0];\n let ay = a[1];\n let az = a[2];\n out[0] = ax + t * (b[0] - ax);\n out[1] = ay + t * (b[1] - ay);\n out[2] = az + t * (b[2] - az);\n return out;\n}\n\n/**\n * Performs a frame rate independant, linear interpolation between two vec3's\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the first operand\n * @param {vec3} b the second operand\n * @param {Number} decay decay constant for interpolation. useful range between 1 and 25, from slow to fast.\n * @param {Number} dt delta time\n * @returns {vec3} out\n */\nexport function smoothLerp(out, a, b, decay, dt) {\n const exp = Math.exp(-decay * dt);\n let ax = a[0];\n let ay = a[1];\n let az = a[2];\n\n out[0] = b[0] + (ax - b[0]) * exp;\n out[1] = b[1] + (ay - b[1]) * exp;\n out[2] = b[2] + (az - b[2]) * exp;\n return out;\n}\n\n/**\n * Transforms the vec3 with a mat4.\n * 4th vector component is implicitly '1'\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the vector to transform\n * @param {mat4} m matrix to transform with\n * @returns {vec3} out\n */\nexport function transformMat4(out, a, m) {\n let x = a[0],\n y = a[1],\n z = a[2];\n let w = m[3] * x + m[7] * y + m[11] * z + m[15];\n w = w || 1.0;\n out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;\n out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;\n out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;\n return out;\n}\n\n/**\n * Same as above but doesn't apply translation.\n * Useful for rays.\n */\nexport function scaleRotateMat4(out, a, m) {\n let x = a[0],\n y = a[1],\n z = a[2];\n let w = m[3] * x + m[7] * y + m[11] * z + m[15];\n w = w || 1.0;\n out[0] = (m[0] * x + m[4] * y + m[8] * z) / w;\n out[1] = (m[1] * x + m[5] * y + m[9] * z) / w;\n out[2] = (m[2] * x + m[6] * y + m[10] * z) / w;\n return out;\n}\n\n/**\n * Transforms the vec3 with a mat3.\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the vector to transform\n * @param {mat3} m the 3x3 matrix to transform with\n * @returns {vec3} out\n */\nexport function transformMat3(out, a, m) {\n let x = a[0],\n y = a[1],\n z = a[2];\n out[0] = x * m[0] + y * m[3] + z * m[6];\n out[1] = x * m[1] + y * m[4] + z * m[7];\n out[2] = x * m[2] + y * m[5] + z * m[8];\n return out;\n}\n\n/**\n * Transforms the vec3 with a quat\n *\n * @param {vec3} out the receiving vector\n * @param {vec3} a the vector to transform\n * @param {quat} q quaternion to transform with\n * @returns {vec3} out\n */\nexport function transformQuat(out, a, q) {\n // benchmarks: https://jsperf.com/quaternion-transform-vec3-implementations-fixed\n\n let x = a[0],\n y = a[1],\n z = a[2];\n let qx = q[0],\n qy = q[1],\n qz = q[2],\n qw = q[3];\n\n let uvx = qy * z - qz * y;\n let uvy = qz * x - qx * z;\n let uvz = qx * y - qy * x;\n\n let uuvx = qy * uvz - qz * uvy;\n let uuvy = qz * uvx - qx * uvz;\n let uuvz = qx * uvy - qy * uvx;\n\n let w2 = qw * 2;\n uvx *= w2;\n uvy *= w2;\n uvz *= w2;\n\n uuvx *= 2;\n uuvy *= 2;\n uuvz *= 2;\n\n out[0] = x + uvx + uuvx;\n out[1] = y + uvy + uuvy;\n out[2] = z + uvz + uuvz;\n return out;\n}\n\n/**\n * Get the angle between two 3D vectors\n * @param {vec3} a The first operand\n * @param {vec3} b The second operand\n * @returns {Number} The angle in radians\n */\nexport const angle = (function () {\n const tempA = [0, 0, 0];\n const tempB = [0, 0, 0];\n\n return function (a, b) {\n copy(tempA, a);\n copy(tempB, b);\n\n normalize(tempA, tempA);\n normalize(tempB, tempB);\n\n let cosine = dot(tempA, tempB);\n\n if (cosine > 1.0) {\n return 0;\n } else if (cosine < -1.0) {\n return Math.PI;\n } else {\n return Math.acos(cosine);\n }\n };\n})();\n\n/**\n * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)\n *\n * @param {vec3} a The first vector.\n * @param {vec3} b The second vector.\n * @returns {Boolean} True if the vectors are equal, false otherwise.\n */\nexport function exactEquals(a, b) {\n return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];\n}\n","import * as Vec3Func from './functions/Vec3Func.js';\n\nexport class Vec3 extends Array {\n constructor(x = 0, y = x, z = x) {\n super(x, y, z);\n return this;\n }\n\n get x() {\n return this[0];\n }\n\n get y() {\n return this[1];\n }\n\n get z() {\n return this[2];\n }\n\n set x(v) {\n this[0] = v;\n }\n\n set y(v) {\n this[1] = v;\n }\n\n set z(v) {\n this[2] = v;\n }\n\n set(x, y = x, z = x) {\n if (x.length) return this.copy(x);\n Vec3Func.set(this, x, y, z);\n return this;\n }\n\n copy(v) {\n Vec3Func.copy(this, v);\n return this;\n }\n\n add(va, vb) {\n if (vb) Vec3Func.add(this, va, vb);\n else Vec3Func.add(this, this, va);\n return this;\n }\n\n sub(va, vb) {\n if (vb) Vec3Func.subtract(this, va, vb);\n else Vec3Func.subtract(this, this, va);\n return this;\n }\n\n multiply(v) {\n if (v.length) Vec3Func.multiply(this, this, v);\n else Vec3Func.scale(this, this, v);\n return this;\n }\n\n divide(v) {\n if (v.length) Vec3Func.divide(this, this, v);\n else Vec3Func.scale(this, this, 1 / v);\n return this;\n }\n\n inverse(v = this) {\n Vec3Func.inverse(this, v);\n return this;\n }\n\n // Can't use 'length' as Array.prototype uses it\n len() {\n return Vec3Func.length(this);\n }\n\n distance(v) {\n if (v) return Vec3Func.distance(this, v);\n else return Vec3Func.length(this);\n }\n\n squaredLen() {\n return Vec3Func.squaredLength(this);\n }\n\n squaredDistance(v) {\n if (v) return Vec3Func.squaredDistance(this, v);\n else return Vec3Func.squaredLength(this);\n }\n\n negate(v = this) {\n Vec3Func.negate(this, v);\n return this;\n }\n\n cross(va, vb) {\n if (vb) Vec3Func.cross(this, va, vb);\n else Vec3Func.cross(this, this, va);\n return this;\n }\n\n scale(v) {\n Vec3Func.scale(this, this, v);\n return this;\n }\n\n normalize() {\n Vec3Func.normalize(this, this);\n return this;\n }\n\n dot(v) {\n return Vec3Func.dot(this, v);\n }\n\n equals(v) {\n return Vec3Func.exactEquals(this, v);\n }\n\n applyMatrix3(mat3) {\n Vec3Func.transformMat3(this, this, mat3);\n return this;\n }\n\n applyMatrix4(mat4) {\n Vec3Func.transformMat4(this, this, mat4);\n return this;\n }\n\n scaleRotateMatrix4(mat4) {\n Vec3Func.scaleRotateMat4(this, this, mat4);\n return this;\n }\n\n applyQuaternion(q) {\n Vec3Func.transformQuat(this, this, q);\n return this;\n }\n\n angle(v) {\n return Vec3Func.angle(this, v);\n }\n\n lerp(v, t) {\n Vec3Func.lerp(this, this, v, t);\n return this;\n }\n\n smoothLerp(v, decay, dt) {\n Vec3Func.smoothLerp(this, this, v, decay, dt);\n return this;\n }\n\n clone() {\n return new Vec3(this[0], this[1], this[2]);\n }\n\n fromArray(a, o = 0) {\n this[0] = a[o];\n this[1] = a[o + 1];\n this[2] = a[o + 2];\n return this;\n }\n\n toArray(a = [], o = 0) {\n a[o] = this[0];\n a[o + 1] = this[1];\n a[o + 2] = this[2];\n return a;\n }\n\n transformDirection(mat4) {\n const x = this[0];\n const y = this[1];\n const z = this[2];\n\n this[0] = mat4[0] * x + mat4[4] * y + mat4[8] * z;\n this[1] = mat4[1] * x + mat4[5] * y + mat4[9] * z;\n this[2] = mat4[2] * x + mat4[6] * y + mat4[10] * z;\n\n return this.normalize();\n }\n}\n","// attribute params\n// {\n// data - typed array eg UInt16Array for indices, Float32Array\n// size - int default 1\n// instanced - default null. Pass divisor amount\n// type - gl enum default gl.UNSIGNED_SHORT for 'index', gl.FLOAT for others\n// normalized - boolean default false\n\n// buffer - gl buffer, if buffer exists, don't need to provide data - although needs position data for bounds calculation\n// stride - default 0 - for when passing in buffer\n// offset - default 0 - for when passing in buffer\n// count - default null - for when passing in buffer\n// min - array - for when passing in buffer\n// max - array - for when passing in buffer\n// }\n\n// TODO: fit in transform feedback\n\nimport { Vec3 } from '../math/Vec3.js';\n\nconst tempVec3 = /* @__PURE__ */ new Vec3();\n\nlet ID = 1;\nlet ATTR_ID = 1;\n\n// To stop inifinite warnings\nlet isBoundsWarned = false;\n\nexport class Geometry {\n constructor(gl, attributes = {}) {\n if (!gl.canvas) console.error('gl not passed as first argument to Geometry');\n this.gl = gl;\n this.attributes = attributes;\n this.id = ID++;\n\n // Store one VAO per program attribute locations order\n this.VAOs = {};\n\n this.drawRange = { start: 0, count: 0 };\n this.instancedCount = 0;\n\n // Unbind current VAO so that new buffers don't get added to active mesh\n this.gl.renderer.bindVertexArray(null);\n this.gl.renderer.currentGeometry = null;\n\n // Alias for state store to avoid redundant calls for global state\n this.glState = this.gl.renderer.state;\n\n // create the buffers\n for (let key in attributes) {\n this.addAttribute(key, attributes[key]);\n }\n }\n\n addAttribute(key, attr) {\n this.attributes[key] = attr;\n\n // Set options\n attr.id = ATTR_ID++; // TODO: currently unused, remove?\n attr.size = attr.size || 1;\n attr.type =\n attr.type ||\n (attr.data.constructor === Float32Array\n ? this.gl.FLOAT\n : attr.data.constructor === Uint16Array\n ? this.gl.UNSIGNED_SHORT\n : this.gl.UNSIGNED_INT); // Uint32Array\n attr.target = key === 'index' ? this.gl.ELEMENT_ARRAY_BUFFER : this.gl.ARRAY_BUFFER;\n attr.normalized = attr.normalized || false;\n attr.stride = attr.stride || 0;\n attr.offset = attr.offset || 0;\n attr.count = attr.count || (attr.stride ? attr.data.byteLength / attr.stride : attr.data.length / attr.size);\n attr.divisor = attr.instanced || 0;\n attr.needsUpdate = false;\n attr.usage = attr.usage || this.gl.STATIC_DRAW;\n\n if (!attr.buffer) {\n // Push data to buffer\n this.updateAttribute(attr);\n }\n\n // Update geometry counts. If indexed, ignore regular attributes\n if (attr.divisor) {\n this.isInstanced = true;\n if (this.instancedCount && this.instancedCount !== attr.count * attr.divisor) {\n console.warn('geometry has multiple instanced buffers of different length');\n return (this.instancedCount = Math.min(this.instancedCount, attr.count * attr.divisor));\n }\n this.instancedCount = attr.count * attr.divisor;\n } else if (key === 'index') {\n this.drawRange.count = attr.count;\n } else if (!this.attributes.index) {\n this.drawRange.count = Math.max(this.drawRange.count, attr.count);\n }\n }\n\n updateAttribute(attr) {\n const isNewBuffer = !attr.buffer;\n if (isNewBuffer) attr.buffer = this.gl.createBuffer();\n if (this.glState.boundBuffer !== attr.buffer) {\n this.gl.bindBuffer(attr.target, attr.buffer);\n this.glState.boundBuffer = attr.buffer;\n }\n if (isNewBuffer) {\n this.gl.bufferData(attr.target, attr.data, attr.usage);\n } else {\n this.gl.bufferSubData(attr.target, 0, attr.data);\n }\n attr.needsUpdate = false;\n }\n\n setIndex(value) {\n this.addAttribute('index', value);\n }\n\n setDrawRange(start, count) {\n this.drawRange.start = start;\n this.drawRange.count = count;\n }\n\n setInstancedCount(value) {\n this.instancedCount = value;\n }\n\n createVAO(program) {\n this.VAOs[program.attributeOrder] = this.gl.renderer.createVertexArray();\n this.gl.renderer.bindVertexArray(this.VAOs[program.attributeOrder]);\n this.bindAttributes(program);\n }\n\n bindAttributes(program) {\n // Link all attributes to program using gl.vertexAttribPointer\n program.attributeLocations.forEach((location, { name, type }) => {\n // If geometry missing a required shader attribute\n if (!this.attributes[name]) {\n console.warn(`active attribute ${name} not being supplied`);\n return;\n }\n\n const attr = this.attributes[name];\n\n this.gl.bindBuffer(attr.target, attr.buffer);\n this.glState.boundBuffer = attr.buffer;\n\n // For matrix attributes, buffer needs to be defined per column\n let numLoc = 1;\n if (type === 35674) numLoc = 2; // mat2\n if (type === 35675) numLoc = 3; // mat3\n if (type === 35676) numLoc = 4; // mat4\n\n const size = attr.size / numLoc;\n const stride = numLoc === 1 ? 0 : numLoc * numLoc * 4;\n const offset = numLoc === 1 ? 0 : numLoc * 4;\n\n for (let i = 0; i < numLoc; i++) {\n this.gl.vertexAttribPointer(location + i, size, attr.type, attr.normalized, attr.stride + stride, attr.offset + i * offset);\n this.gl.enableVertexAttribArray(location + i);\n\n // For instanced attributes, divisor needs to be set.\n // For firefox, need to set back to 0 if non-instanced drawn after instanced. Else won't render\n this.gl.renderer.vertexAttribDivisor(location + i, attr.divisor);\n }\n });\n\n // Bind indices if geometry indexed\n if (this.attributes.index) this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.attributes.index.buffer);\n }\n\n draw({ program, mode = this.gl.TRIANGLES }) {\n if (this.gl.renderer.currentGeometry !== `${this.id}_${program.attributeOrder}`) {\n if (!this.VAOs[program.attributeOrder]) this.createVAO(program);\n this.gl.renderer.bindVertexArray(this.VAOs[program.attributeOrder]);\n this.gl.renderer.currentGeometry = `${this.id}_${program.attributeOrder}`;\n }\n\n // Check if any attributes need updating\n program.attributeLocations.forEach((location, { name }) => {\n const attr = this.attributes[name];\n if (attr.needsUpdate) this.updateAttribute(attr);\n });\n\n // For drawElements, offset needs to be multiple of type size\n let indexBytesPerElement = 2;\n if (this.attributes.index?.type === this.gl.UNSIGNED_INT) indexBytesPerElement = 4;\n\n if (this.isInstanced) {\n if (this.attributes.index) {\n this.gl.renderer.drawElementsInstanced(\n mode,\n this.drawRange.count,\n this.attributes.index.type,\n this.attributes.index.offset + this.drawRange.start * indexBytesPerElement,\n this.instancedCount\n );\n } else {\n this.gl.renderer.drawArraysInstanced(mode, this.drawRange.start, this.drawRange.count, this.instancedCount);\n }\n } else {\n if (this.attributes.index) {\n this.gl.drawElements(\n mode,\n this.drawRange.count,\n this.attributes.index.type,\n this.attributes.index.offset + this.drawRange.start * indexBytesPerElement\n );\n } else {\n this.gl.drawArrays(mode, this.drawRange.start, this.drawRange.count);\n }\n }\n }\n\n getPosition() {\n // Use position buffer, or min/max if available\n const attr = this.attributes.position;\n // if (attr.min) return [...attr.min, ...attr.max];\n if (attr.data) return attr;\n if (isBoundsWarned) return;\n console.warn('No position buffer data found to compute bounds');\n return (isBoundsWarned = true);\n }\n\n computeBoundingBox(attr) {\n if (!attr) attr = this.getPosition();\n const array = attr.data;\n // Data loaded shouldn't haave stride, only buffers\n // const stride = attr.stride ? attr.stride / array.BYTES_PER_ELEMENT : attr.size;\n const stride = attr.size;\n\n if (!this.bounds) {\n this.bounds = {\n min: new Vec3(),\n max: new Vec3(),\n center: new Vec3(),\n scale: new Vec3(),\n radius: Infinity,\n };\n }\n\n const min = this.bounds.min;\n const max = this.bounds.max;\n const center = this.bounds.center;\n const scale = this.bounds.scale;\n\n min.set(+Infinity);\n max.set(-Infinity);\n\n // TODO: check size of position (eg triangle with Vec2)\n for (let i = 0, l = array.length; i < l; i += stride) {\n const x = array[i];\n const y = array[i + 1];\n const z = array[i + 2];\n\n min.x = Math.min(x, min.x);\n min.y = Math.min(y, min.y);\n min.z = Math.min(z, min.z);\n\n max.x = Math.max(x, max.x);\n max.y = Math.max(y, max.y);\n max.z = Math.max(z, max.z);\n }\n\n scale.sub(max, min);\n center.add(min, max).divide(2);\n }\n\n computeBoundingSphere(attr) {\n if (!attr) attr = this.getPosition();\n const array = attr.data;\n // Data loaded shouldn't haave stride, only buffers\n // const stride = attr.stride ? attr.stride / array.BYTES_PER_ELEMENT : attr.size;\n const stride = attr.size;\n\n if (!this.bounds) this.computeBoundingBox(attr);\n\n let maxRadiusSq = 0;\n for (let i = 0, l = array.length; i < l; i += stride) {\n tempVec3.fromArray(array, i);\n maxRadiusSq = Math.max(maxRadiusSq, this.bounds.center.squaredDistance(tempVec3));\n }\n\n this.bounds.radius = Math.sqrt(maxRadiusSq);\n }\n\n remove() {\n for (let key in this.VAOs) {\n this.gl.renderer.deleteVertexArray(this.VAOs[key]);\n delete this.VAOs[key];\n }\n for (let key in this.attributes) {\n this.gl.deleteBuffer(this.attributes[key].buffer);\n delete this.attributes[key];\n }\n }\n}\n","// TODO: upload empty texture if null ? maybe not\n// TODO: upload identity matrix if null ?\n// TODO: sampler Cube\n\nlet ID = 1;\n\n// cache of typed arrays used to flatten uniform arrays\nconst arrayCacheF32 = {};\n\nexport class Program {\n constructor(\n gl,\n {\n vertex,\n fragment,\n uniforms = {},\n\n transparent = false,\n cullFace = gl.BACK,\n frontFace = gl.CCW,\n depthTest = true,\n depthWrite = true,\n depthFunc = gl.LEQUAL,\n } = {}\n ) {\n if (!gl.canvas) console.error('gl not passed as first argument to Program');\n this.gl = gl;\n this.uniforms = uniforms;\n this.id = ID++;\n\n if (!vertex) console.warn('vertex shader not supplied');\n if (!fragment) console.warn('fragment shader not supplied');\n\n // Store program state\n this.transparent = transparent;\n this.cullFace = cullFace;\n this.frontFace = frontFace;\n this.depthTest = depthTest;\n this.depthWrite = depthWrite;\n this.depthFunc = depthFunc;\n this.blendFunc = {};\n this.blendEquation = {};\n this.stencilFunc = {};\n this.stencilOp = {}\n\n // set default blendFunc if transparent flagged\n if (this.transparent && !this.blendFunc.src) {\n if (this.gl.renderer.premultipliedAlpha) this.setBlendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);\n else this.setBlendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);\n }\n\n // Create empty shaders and attach to program\n this.vertexShader = gl.createShader(gl.VERTEX_SHADER);\n this.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n this.program = gl.createProgram();\n gl.attachShader(this.program, this.vertexShader);\n gl.attachShader(this.program, this.fragmentShader);\n\n // Compile shaders with source\n this.setShaders({ vertex, fragment });\n }\n\n setShaders({ vertex, fragment }) {\n if (vertex) {\n // compile vertex shader and log errors\n this.gl.shaderSource(this.vertexShader, vertex);\n this.gl.compileShader(this.vertexShader);\n if (this.gl.getShaderInfoLog(this.vertexShader) !== '') {\n console.warn(`${this.gl.getShaderInfoLog(this.vertexShader)}\\nVertex Shader\\n${addLineNumbers(vertex)}`);\n }\n }\n\n if (fragment) {\n // compile fragment shader and log errors\n this.gl.shaderSource(this.fragmentShader, fragment);\n this.gl.compileShader(this.fragmentShader);\n if (this.gl.getShaderInfoLog(this.fragmentShader) !== '') {\n console.warn(`${this.gl.getShaderInfoLog(this.fragmentShader)}\\nFragment Shader\\n${addLineNumbers(fragment)}`);\n }\n }\n\n // compile program and log errors\n this.gl.linkProgram(this.program);\n if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {\n return console.warn(this.gl.getProgramInfoLog(this.program));\n }\n\n // Get active uniform locations\n this.uniformLocations = new Map();\n let numUniforms = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_UNIFORMS);\n for (let uIndex = 0; uIndex < numUniforms; uIndex++) {\n let uniform = this.gl.getActiveUniform(this.program, uIndex);\n this.uniformLocations.set(uniform, this.gl.getUniformLocation(this.program, uniform.name));\n\n // split uniforms' names to separate array and struct declarations\n const split = uniform.name.match(/(\\w+)/g);\n\n uniform.uniformName = split[0];\n uniform.nameComponents = split.slice(1);\n }\n\n // Get active attribute locations\n this.attributeLocations = new Map();\n const locations = [];\n const numAttribs = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_ATTRIBUTES);\n for (let aIndex = 0; aIndex < numAttribs; aIndex++) {\n const attribute = this.gl.getActiveAttrib(this.program, aIndex);\n const location = this.gl.getAttribLocation(this.program, attribute.name);\n // Ignore special built-in inputs. eg gl_VertexID, gl_InstanceID\n if (location === -1) continue;\n locations[location] = attribute.name;\n this.attributeLocations.set(attribute, location);\n }\n this.attributeOrder = locations.join('');\n }\n\n setBlendFunc(src, dst, srcAlpha, dstAlpha) {\n this.blendFunc.src = src;\n this.blendFunc.dst = dst;\n this.blendFunc.srcAlpha = srcAlpha;\n this.blendFunc.dstAlpha = dstAlpha;\n if (src) this.transparent = true;\n }\n\n setBlendEquation(modeRGB, modeAlpha) {\n this.blendEquation.modeRGB = modeRGB;\n this.blendEquation.modeAlpha = modeAlpha;\n }\n\n setStencilFunc(func, ref, mask) {\n this.stencilRef = ref;\n this.stencilFunc.func = func;\n this.stencilFunc.ref = ref;\n this.stencilFunc.mask = mask;\n }\n\n setStencilOp(stencilFail, depthFail, depthPass) {\n this.stencilOp.stencilFail = stencilFail;\n this.stencilOp.depthFail = depthFail;\n this.stencilOp.depthPass = depthPass;\n }\n\n applyState() {\n if (this.depthTest) this.gl.renderer.enable(this.gl.DEPTH_TEST);\n else this.gl.renderer.disable(this.gl.DEPTH_TEST);\n\n if (this.cullFace) this.gl.renderer.enable(this.gl.CULL_FACE);\n else this.gl.renderer.disable(this.gl.CULL_FACE);\n\n if (this.blendFunc.src) this.gl.renderer.enable(this.gl.BLEND);\n else this.gl.renderer.disable(this.gl.BLEND);\n\n if (this.cullFace) this.gl.renderer.setCullFace(this.cullFace);\n this.gl.renderer.setFrontFace(this.frontFace);\n this.gl.renderer.setDepthMask(this.depthWrite);\n this.gl.renderer.setDepthFunc(this.depthFunc);\n if (this.blendFunc.src) this.gl.renderer.setBlendFunc(this.blendFunc.src, this.blendFunc.dst, this.blendFunc.srcAlpha, this.blendFunc.dstAlpha);\n this.gl.renderer.setBlendEquation(this.blendEquation.modeRGB, this.blendEquation.modeAlpha);\n\n if(this.stencilFunc.func || this.stencilOp.stencilFail) this.gl.renderer.enable(this.gl.STENCIL_TEST)\n else this.gl.renderer.disable(this.gl.STENCIL_TEST)\n\n this.gl.renderer.setStencilFunc(this.stencilFunc.func, this.stencilFunc.ref, this.stencilFunc.mask)\n this.gl.renderer.setStencilOp(this.stencilOp.stencilFail, this.stencilOp.depthFail, this.stencilOp.depthPass)\n\n }\n\n use({ flipFaces = false } = {}) {\n let textureUnit = -1;\n const programActive = this.gl.renderer.state.currentProgram === this.id;\n\n // Avoid gl call if program already in use\n if (!programActive) {\n this.gl.useProgram(this.program);\n this.gl.renderer.state.currentProgram = this.id;\n }\n\n // Set only the active uniforms found in the shader\n this.uniformLocations.forEach((location, activeUniform) => {\n let uniform = this.uniforms[activeUniform.uniformName];\n\n for (const component of activeUniform.nameComponents) {\n if (!uniform) break;\n\n if (component in uniform) {\n uniform = uniform[component];\n } else if (Array.isArray(uniform.value)) {\n break;\n } else {\n uniform = undefined;\n break;\n }\n }\n\n if (!uniform) {\n return warn(`Active uniform ${activeUniform.name} has not been supplied`);\n }\n\n if (uniform && uniform.value === undefined) {\n return warn(`${activeUniform.name} uniform is missing a value parameter`);\n }\n\n if (uniform.value.texture) {\n textureUnit = textureUnit + 1;\n\n // Check if texture needs to be updated\n uniform.value.update(textureUnit);\n return setUniform(this.gl, activeUniform.type, location, textureUnit);\n }\n\n // For texture arrays, set uniform as an array of texture units instead of just one\n if (uniform.value.length && uniform.value[0].texture) {\n const textureUnits = [];\n uniform.value.forEach((value) => {\n textureUnit = textureUnit + 1;\n value.update(textureUnit);\n textureUnits.push(textureUnit);\n });\n\n return setUniform(this.gl, activeUniform.type, location, textureUnits);\n }\n\n setUniform(this.gl, activeUniform.type, location, uniform.value);\n });\n\n this.applyState();\n if (flipFaces) this.gl.renderer.setFrontFace(this.frontFace === this.gl.CCW ? this.gl.CW : this.gl.CCW);\n }\n\n remove() {\n this.gl.deleteProgram(this.program);\n }\n}\n\nfunction setUniform(gl, type, location, value) {\n value = value.length ? flatten(value) : value;\n const setValue = gl.renderer.state.uniformLocations.get(location);\n\n // Avoid redundant uniform commands\n if (value.length) {\n if (setValue === undefined || setValue.length !== value.length) {\n // clone array to store as cache\n gl.renderer.state.uniformLocations.set(location, value.slice(0));\n } else {\n if (arraysEqual(setValue, value)) return;\n\n // Update cached array values\n setValue.set ? setValue.set(value) : setArray(setValue, value);\n gl.renderer.state.uniformLocations.set(location, setValue);\n }\n } else {\n if (setValue === value) return;\n gl.renderer.state.uniformLocations.set(location, value);\n }\n\n switch (type) {\n case 5126:\n return value.length ? gl.uniform1fv(location, value) : gl.uniform1f(location, value); // FLOAT\n case 35664:\n return gl.uniform2fv(location, value); // FLOAT_VEC2\n case 35665:\n return gl.uniform3fv(location, value); // FLOAT_VEC3\n case 35666:\n return gl.uniform4fv(location, value); // FLOAT_VEC4\n case 35670: // BOOL\n case 5124: // INT\n case 35678: // SAMPLER_2D\n case 36306: // U_SAMPLER_2D\n case 35680: // SAMPLER_CUBE\n case 36289: // SAMPLER_2D_ARRAY\n return value.length ? gl.uniform1iv(location, value) : gl.uniform1i(location, value); // SAMPLER_CUBE\n case 35671: // BOOL_VEC2\n case 35667:\n return gl.uniform2iv(location, value); // INT_VEC2\n case 35672: // BOOL_VEC3\n case 35668:\n return gl.uniform3iv(location, value); // INT_VEC3\n case 35673: // BOOL_VEC4\n case 35669:\n return gl.uniform4iv(location, value); // INT_VEC4\n case 35674:\n return gl.uniformMatrix2fv(location, false, value); // FLOAT_MAT2\n case 35675:\n return gl.uniformMatrix3fv(location, false, value); // FLOAT_MAT3\n case 35676:\n return gl.uniformMatrix4fv(location, false, value); // FLOAT_MAT4\n }\n}\n\nfunction addLineNumbers(string) {\n let lines = string.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n lines[i] = i + 1 + ': ' + lines[i];\n }\n return lines.join('\\n');\n}\n\nfunction flatten(a) {\n const arrayLen = a.length;\n const valueLen = a[0].length;\n if (valueLen === undefined) return a;\n const length = arrayLen * valueLen;\n let value = arrayCacheF32[length];\n if (!value) arrayCacheF32[length] = value = new Float32Array(length);\n for (let i = 0; i < arrayLen; i++) value.set(a[i], i * valueLen);\n return value;\n}\n\nfunction arraysEqual(a, b) {\n if (a.length !== b.length) return false;\n for (let i = 0, l = a.length; i < l; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction setArray(a, b) {\n for (let i = 0, l = a.length; i < l; i++) {\n a[i] = b[i];\n }\n}\n\nlet warnCount = 0;\nfunction warn(message) {\n if (warnCount > 100) return;\n console.warn(message);\n warnCount++;\n if (warnCount > 100) console.warn('More than 100 program warnings - stopping logs.');\n}\n","import { Vec3 } from '../math/Vec3.js';\n\n// TODO: Handle context loss https://www.khronos.org/webgl/wiki/HandlingContextLost\n\n// Not automatic - devs to use these methods manually\n// gl.colorMask( colorMask, colorMask, colorMask, colorMask );\n// gl.clearColor( r, g, b, a );\n// gl.stencilMask( stencilMask );\n// gl.stencilFunc( stencilFunc, stencilRef, stencilMask );\n// gl.stencilOp( stencilFail, stencilZFail, stencilZPass );\n// gl.clearStencil( stencil );\n\nconst tempVec3 = /* @__PURE__ */ new Vec3();\nlet ID = 1;\n\nexport class Renderer {\n constructor({\n canvas = document.createElement('canvas'),\n width = 300,\n height = 150,\n dpr = 1,\n alpha = false,\n depth = true,\n stencil = false,\n antialias = false,\n premultipliedAlpha = false,\n preserveDrawingBuffer = false,\n powerPreference = 'default',\n autoClear = true,\n webgl = 2,\n } = {}) {\n const attributes = { alpha, depth, stencil, antialias, premultipliedAlpha, preserveDrawingBuffer, powerPreference };\n this.dpr = dpr;\n this.alpha = alpha;\n this.color = true;\n this.depth = depth;\n this.stencil = stencil;\n this.premultipliedAlpha = premultipliedAlpha;\n this.autoClear = autoClear;\n this.id = ID++;\n\n // Attempt WebGL2 unless forced to 1, if not supported fallback to WebGL1\n if (webgl === 2) this.gl = canvas.getContext('webgl2', attributes);\n this.isWebgl2 = !!this.gl;\n if (!this.gl) this.gl = canvas.getContext('webgl', attributes);\n if (!this.gl) console.error('unable to create webgl context');\n\n // Attach renderer to gl so that all classes have access to internal state functions\n this.gl.renderer = this;\n\n // initialise size values\n this.setSize(width, height);\n\n // gl state stores to avoid redundant calls on methods used internally\n this.state = {};\n this.state.blendFunc = { src: this.gl.ONE, dst: this.gl.ZERO };\n this.state.blendEquation = { modeRGB: this.gl.FUNC_ADD };\n this.state.cullFace = false;\n this.state.frontFace = this.gl.CCW;\n this.state.depthMask = true;\n this.state.depthFunc = this.gl.LEQUAL;\n this.state.premultiplyAlpha = false;\n this.state.flipY = false;\n this.state.unpackAlignment = 4;\n this.state.framebuffer = null;\n this.state.viewport = { x: 0, y: 0, width: null, height: null };\n this.state.textureUnits = [];\n this.state.activeTextureUnit = 0;\n this.state.boundBuffer = null;\n this.state.uniformLocations = new Map();\n this.state.currentProgram = null;\n\n // store requested extensions\n this.extensions = {};\n\n // Initialise extra format types\n if (this.isWebgl2) {\n this.getExtension('EXT_color_buffer_float');\n this.getExtension('OES_texture_float_linear');\n } else {\n this.getExtension('OES_texture_float');\n this.getExtension('OES_texture_float_linear');\n this.getExtension('OES_texture_half_float');\n this.getExtension('OES_texture_half_float_linear');\n this.getExtension('OES_element_index_uint');\n this.getExtension('OES_standard_derivatives');\n this.getExtension('EXT_sRGB');\n this.getExtension('WEBGL_depth_texture');\n this.getExtension('WEBGL_draw_buffers');\n }\n this.getExtension('WEBGL_compressed_texture_astc');\n this.getExtension('EXT_texture_compression_bptc');\n this.getExtension('WEBGL_compressed_texture_s3tc');\n this.getExtension('WEBGL_compressed_texture_etc1');\n this.getExtension('WEBGL_compressed_texture_pvrtc');\n this.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');\n\n // Create method aliases using extension (WebGL1) or native if available (WebGL2)\n this.vertexAttribDivisor = this.getExtension('ANGLE_instanced_arrays', 'vertexAttribDivisor', 'vertexAttribDivisorANGLE');\n this.drawArraysInstanced = this.getExtension('ANGLE_instanced_arrays', 'drawArraysInstanced', 'drawArraysInstancedANGLE');\n this.drawElementsInstanced = this.getExtension('ANGLE_instanced_arrays', 'drawElementsInstanced', 'drawElementsInstancedANGLE');\n this.createVertexArray = this.getExtension('OES_vertex_array_object', 'createVertexArray', 'createVertexArrayOES');\n this.bindVertexArray = this.getExtension('OES_vertex_array_object', 'bindVertexArray', 'bindVertexArrayOES');\n this.deleteVertexArray = this.getExtension('OES_vertex_array_object', 'deleteVertexArray', 'deleteVertexArrayOES');\n this.drawBuffers = this.getExtension('WEBGL_draw_buffers', 'drawBuffers', 'drawBuffersWEBGL');\n\n // Store device parameters\n this.parameters = {};\n this.parameters.maxTextureUnits = this.gl.getParameter(this.gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);\n this.parameters.maxAnisotropy = this.getExtension('EXT_texture_filter_anisotropic')\n ? this.gl.getParameter(this.getExtension('EXT_texture_filter_anisotropic').MAX_TEXTURE_MAX_ANISOTROPY_EXT)\n : 0;\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n\n this.gl.canvas.width = width * this.dpr;\n this.gl.canvas.height = height * this.dpr;\n\n if (!this.gl.canvas.style) return;\n Object.assign(this.gl.canvas.style, {\n width: width + 'px',\n height: height + 'px',\n });\n }\n\n setViewport(width, height, x = 0, y = 0) {\n if (this.state.viewport.width === width && this.state.viewport.height === height) return;\n this.state.viewport.width = width;\n this.state.viewport.height = height;\n this.state.viewport.x = x;\n this.state.viewport.y = y;\n this.gl.viewport(x, y, width, height);\n }\n\n setScissor(width, height, x = 0, y = 0) {\n this.gl.scissor(x, y, width, height);\n }\n\n enable(id) {\n if (this.state[id] === true) return;\n this.gl.enable(id);\n this.state[id] = true;\n }\n\n disable(id) {\n if (this.state[id] === false) return;\n this.gl.disable(id);\n this.state[id] = false;\n }\n\n setBlendFunc(src, dst, srcAlpha, dstAlpha) {\n if (\n this.state.blendFunc.src === src &&\n this.state.blendFunc.dst === dst &&\n this.state.blendFunc.srcAlpha === srcAlpha &&\n this.state.blendFunc.dstAlpha === dstAlpha\n )\n return;\n this.state.blendFunc.src = src;\n this.state.blendFunc.dst = dst;\n this.state.blendFunc.srcAlpha = srcAlpha;\n this.state.blendFunc.dstAlpha = dstAlpha;\n if (srcAlpha !== undefined) this.gl.blendFuncSeparate(src, dst, srcAlpha, dstAlpha);\n else this.gl.blendFunc(src, dst);\n }\n\n setBlendEquation(modeRGB, modeAlpha) {\n modeRGB = modeRGB || this.gl.FUNC_ADD;\n if (this.state.blendEquation.modeRGB === modeRGB && this.state.blendEquation.modeAlpha === modeAlpha) return;\n this.state.blendEquation.modeRGB = modeRGB;\n this.state.blendEquation.modeAlpha = modeAlpha;\n if (modeAlpha !== undefined) this.gl.blendEquationSeparate(modeRGB, modeAlpha);\n else this.gl.blendEquation(modeRGB);\n }\n\n setCullFace(value) {\n if (this.state.cullFace === value) return;\n this.state.cullFace = value;\n this.gl.cullFace(value);\n }\n\n setFrontFace(value) {\n if (this.state.frontFace === value) return;\n this.state.frontFace = value;\n this.gl.frontFace(value);\n }\n\n setDepthMask(value) {\n if (this.state.depthMask === value) return;\n this.state.depthMask = value;\n this.gl.depthMask(value);\n }\n\n setDepthFunc(value) {\n if (this.state.depthFunc === value) return;\n this.state.depthFunc = value;\n this.gl.depthFunc(value);\n }\n\n setStencilMask(value) {\n if(this.state.stencilMask === value) return;\n this.state.stencilMask = value;\n this.gl.stencilMask(value)\n }\n\n setStencilFunc(func, ref, mask) {\n\n if((this.state.stencilFunc === func) &&\n (this.state.stencilRef === ref) &&\n (this.state.stencilFuncMask === mask)\n ) return;\n\n this.state.stencilFunc = func || this.gl.ALWAYS;\n this.state.stencilRef = ref || 0;\n this.state.stencilFuncMask = mask || 0;\n\n this.gl.stencilFunc(func || this.gl.ALWAYS, ref || 0, mask || 0);\n }\n\n setStencilOp(stencilFail, depthFail, depthPass) {\n\n if(this.state.stencilFail === stencilFail &&\n this.state.stencilDepthFail === depthFail &&\n this.state.stencilDepthPass === depthPass\n ) return;\n\n this.state.stencilFail = stencilFail;\n this.state.stencilDepthFail = depthFail;\n this.state.stencilDepthPass = depthPass;\n \n this.gl.stencilOp(stencilFail, depthFail, depthPass);\n \n }\n\n activeTexture(value) {\n if (this.state.activeTextureUnit === value) return;\n this.state.activeTextureUnit = value;\n this.gl.activeTexture(this.gl.TEXTURE0 + value);\n }\n\n bindFramebuffer({ target = this.gl.FRAMEBUFFER, buffer = null } = {}) {\n if (this.state.framebuffer === buffer) return;\n this.state.framebuffer = buffer;\n this.gl.bindFramebuffer(target, buffer);\n }\n\n getExtension(extension, webgl2Func, extFunc) {\n // if webgl2 function supported, return func bound to gl context\n if (webgl2Func && this.gl[webgl2Func]) return this.gl[webgl2Func].bind(this.gl);\n\n // fetch extension once only\n if (!this.extensions[extension]) {\n this.extensions[extension] = this.gl.getExtension(extension);\n }\n\n // return extension if no function requested\n if (!webgl2Func) return this.extensions[extension];\n\n // Return null if extension not supported\n if (!this.extensions[extension]) return null;\n\n // return extension function, bound to extension\n return this.extensions[extension][extFunc].bind(this.extensions[extension]);\n }\n\n sortOpaque(a, b) {\n if (a.renderOrder !== b.renderOrder) {\n return a.renderOrder - b.renderOrder;\n } else if (a.program.id !== b.program.id) {\n return a.program.id - b.program.id;\n } else if (a.zDepth !== b.zDepth) {\n return a.zDepth - b.zDepth;\n } else {\n return b.id - a.id;\n }\n }\n\n sortTransparent(a, b) {\n if (a.renderOrder !== b.renderOrder) {\n return a.renderOrder - b.renderOrder;\n }\n if (a.zDepth !== b.zDepth) {\n return b.zDepth - a.zDepth;\n } else {\n return b.id - a.id;\n }\n }\n\n sortUI(a, b) {\n if (a.renderOrder !== b.renderOrder) {\n return a.renderOrder - b.renderOrder;\n } else if (a.program.id !== b.program.id) {\n return a.program.id - b.program.id;\n } else {\n re