UNPKG

@gltf-transform/core

Version:

glTF 2.0 SDK for JavaScript and TypeScript, on Web and Node.js.

1,490 lines 210 kB
import { $attributes, $immutableKeys, Graph, Graph as Graph$1, GraphEdge, GraphEdge as GraphEdge$1, GraphNode, RefList, RefList as RefList$1, RefMap, RefMap as RefMap$1, RefSet, RefSet as RefSet$1 } from "property-graph"; //#region src/constants.ts /** * Current version of the package. * @hidden */ const VERSION = `v4.4.2`; /** @hidden */ const GLB_BUFFER = "@glb.bin"; /** String IDs for core {@link Property} types. */ let PropertyType = /* @__PURE__ */ function(PropertyType) { PropertyType["ACCESSOR"] = "Accessor"; PropertyType["ANIMATION"] = "Animation"; PropertyType["ANIMATION_CHANNEL"] = "AnimationChannel"; PropertyType["ANIMATION_SAMPLER"] = "AnimationSampler"; PropertyType["BUFFER"] = "Buffer"; PropertyType["CAMERA"] = "Camera"; PropertyType["MATERIAL"] = "Material"; PropertyType["MESH"] = "Mesh"; PropertyType["PRIMITIVE"] = "Primitive"; PropertyType["PRIMITIVE_TARGET"] = "PrimitiveTarget"; PropertyType["NODE"] = "Node"; PropertyType["ROOT"] = "Root"; PropertyType["SCENE"] = "Scene"; PropertyType["SKIN"] = "Skin"; PropertyType["TEXTURE"] = "Texture"; PropertyType["TEXTURE_INFO"] = "TextureInfo"; return PropertyType; }({}); /** Vertex layout method. */ let VertexLayout = /* @__PURE__ */ function(VertexLayout) { /** * Stores vertex attributes in a single buffer view per mesh primitive. Interleaving vertex * data may improve performance by reducing page-thrashing in GPU memory. */ VertexLayout["INTERLEAVED"] = "interleaved"; /** * Stores each vertex attribute in a separate buffer view. May decrease performance by causing * page-thrashing in GPU memory. Some 3D engines may prefer this layout, e.g. for simplicity. */ VertexLayout["SEPARATE"] = "separate"; return VertexLayout; }({}); /** Accessor usage. */ let BufferViewUsage$1 = /* @__PURE__ */ function(BufferViewUsage) { BufferViewUsage["ARRAY_BUFFER"] = "ARRAY_BUFFER"; BufferViewUsage["ELEMENT_ARRAY_BUFFER"] = "ELEMENT_ARRAY_BUFFER"; BufferViewUsage["INVERSE_BIND_MATRICES"] = "INVERSE_BIND_MATRICES"; BufferViewUsage["OTHER"] = "OTHER"; BufferViewUsage["SPARSE"] = "SPARSE"; return BufferViewUsage; }({}); /** Texture channels. */ let TextureChannel = /* @__PURE__ */ function(TextureChannel) { TextureChannel[TextureChannel["R"] = 4096] = "R"; TextureChannel[TextureChannel["G"] = 256] = "G"; TextureChannel[TextureChannel["B"] = 16] = "B"; TextureChannel[TextureChannel["A"] = 1] = "A"; return TextureChannel; }({}); let Format = /* @__PURE__ */ function(Format) { Format["GLTF"] = "GLTF"; Format["GLB"] = "GLB"; return Format; }({}); var UnsupportedArray = class extends Float32Array { constructor() { super(); throw new Error("Unsupported typed array instantiation."); } }; const ComponentTypeToTypedArray = { "5120": Int8Array, "5121": Uint8Array, "5122": Int16Array, "5123": Uint16Array, "5125": Uint32Array, "5131": typeof Float16Array !== "undefined" ? Float16Array : UnsupportedArray, "5126": Float32Array, "5130": Float64Array }; //#endregion //#region src/utils/buffer-utils.ts /** * *Common utilities for working with Uint8Array and Buffer objects.* * * @category Utilities */ var BufferUtils = class { /** Creates a byte array from a Data URI. */ static createBufferFromDataURI(dataURI) { if (typeof Buffer === "undefined") { const byteString = atob(dataURI.split(",")[1]); const ia = new Uint8Array(byteString.length); for (let i = 0; i < byteString.length; i++) ia[i] = byteString.charCodeAt(i); return ia; } else { const data = dataURI.split(",")[1]; const isBase64 = dataURI.indexOf("base64") >= 0; return Buffer.from(data, isBase64 ? "base64" : "utf8"); } } /** Encodes text to a byte array. */ static encodeText(text) { return new TextEncoder().encode(text); } /** Decodes a byte array to text. */ static decodeText(array) { return new TextDecoder().decode(array); } /** * Concatenates N byte arrays. */ static concat(arrays) { let totalByteLength = 0; for (const array of arrays) totalByteLength += array.byteLength; const result = new Uint8Array(totalByteLength); let byteOffset = 0; for (const array of arrays) { result.set(array, byteOffset); byteOffset += array.byteLength; } return result; } /** * Pads a Uint8Array to the next 4-byte boundary. * * Reference: [glTF → Data Alignment](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment) */ static pad(srcArray, paddingByte = 0) { const paddedLength = this.padNumber(srcArray.byteLength); if (paddedLength === srcArray.byteLength) return srcArray; const dstArray = new Uint8Array(paddedLength); dstArray.set(srcArray); if (paddingByte !== 0) for (let i = srcArray.byteLength; i < paddedLength; i++) dstArray[i] = paddingByte; return dstArray; } /** Pads a number to 4-byte boundaries. */ static padNumber(v) { return Math.ceil(v / 4) * 4; } /** Returns true if given byte array instances are equal. */ static equals(a, b) { if (a === b) return true; if (a.byteLength !== b.byteLength) return false; let i = a.byteLength; while (i--) if (a[i] !== b[i]) return false; return true; } /** * Returns a Uint8Array view of a typed array, with the same underlying ArrayBuffer. * * A shorthand for: * * ```js * const buffer = new Uint8Array( * array.buffer, * array.byteOffset + byteOffset, * Math.min(array.byteLength, byteLength) * ); * ``` * */ static toView(a, byteOffset = 0, byteLength = Infinity) { return new Uint8Array(a.buffer, a.byteOffset + byteOffset, Math.min(a.byteLength, byteLength)); } static assertView(view) { if (view && !ArrayBuffer.isView(view)) throw new Error(`Method requires Uint8Array parameter; received "${typeof view}".`); return view; } }; //#endregion //#region src/utils/color-utils.ts /** * *Common utilities for working with colors in vec3, vec4, or hexadecimal form.* * * Provides methods to convert linear components (vec3, vec4) to sRGB hex values. All colors in * the glTF specification, excluding color textures, are linear. Hexadecimal values, in sRGB * colorspace, are accessible through helper functions in the API as a convenience. * * ```typescript * // Hex (sRGB) to factor (linear). * const factor = ColorUtils.hexToFactor(0xFFCCCC, []); * * // Factor (linear) to hex (sRGB). * const hex = ColorUtils.factorToHex([1, .25, .25]) * ``` * * @category Utilities */ var ColorUtils = class { /** * Converts sRGB hexadecimal to linear components. * @typeParam T vec3 or vec4 linear components. */ static hexToFactor(hex, target) { hex = Math.floor(hex); const _target = target; _target[0] = (hex >> 16 & 255) / 255; _target[1] = (hex >> 8 & 255) / 255; _target[2] = (hex & 255) / 255; return this.convertSRGBToLinear(target, target); } /** * Converts linear components to sRGB hexadecimal. * @typeParam T vec3 or vec4 linear components. */ static factorToHex(factor) { const target = [...factor]; const [r, g, b] = this.convertLinearToSRGB(factor, target); return r * 255 << 16 ^ g * 255 << 8 ^ b * 255 << 0; } /** * Converts sRGB components to linear components. * @typeParam T vec3 or vec4 linear components. */ static convertSRGBToLinear(source, target) { const _source = source; const _target = target; for (let i = 0; i < 3; i++) _target[i] = _source[i] < .04045 ? _source[i] * .0773993808 : Math.pow(_source[i] * .9478672986 + .0521327014, 2.4); return target; } /** * Converts linear components to sRGB components. * @typeParam T vec3 or vec4 linear components. */ static convertLinearToSRGB(source, target) { const _source = source; const _target = target; for (let i = 0; i < 3; i++) _target[i] = _source[i] < .0031308 ? _source[i] * 12.92 : 1.055 * Math.pow(_source[i], .41666) - .055; return target; } }; //#endregion //#region src/utils/image-utils.ts /** JPEG image support. */ var JPEGImageUtils = class { match(array) { return array.length >= 3 && array[0] === 255 && array[1] === 216 && array[2] === 255; } getSize(array) { let view = new DataView(array.buffer, array.byteOffset + 4); let i, next; while (view.byteLength) { i = view.getUint16(0, false); validateJPEGBuffer(view, i); next = view.getUint8(i + 1); if (next === 192 || next === 193 || next === 194) return [view.getUint16(i + 7, false), view.getUint16(i + 5, false)]; view = new DataView(array.buffer, view.byteOffset + i + 2); } throw new TypeError("Invalid JPG, no size found"); } getChannels(_buffer) { return 3; } }; /** * PNG image support. * * PNG signature: 'PNG\r\n\x1a\n' * PNG image header chunk name: 'IHDR' */ var PNGImageUtils = class PNGImageUtils { static PNG_FRIED_CHUNK_NAME = "CgBI"; match(array) { return array.length >= 8 && array[0] === 137 && array[1] === 80 && array[2] === 78 && array[3] === 71 && array[4] === 13 && array[5] === 10 && array[6] === 26 && array[7] === 10; } getSize(array) { const view = new DataView(array.buffer, array.byteOffset); if (BufferUtils.decodeText(array.slice(12, 16)) === PNGImageUtils.PNG_FRIED_CHUNK_NAME) return [view.getUint32(32, false), view.getUint32(36, false)]; return [view.getUint32(16, false), view.getUint32(20, false)]; } getChannels(_buffer) { return 4; } }; /** * *Common utilities for working with image data.* * * @category Utilities */ var ImageUtils = class { static impls = { "image/jpeg": new JPEGImageUtils(), "image/png": new PNGImageUtils() }; /** Registers support for a new image format; useful for certain extensions. */ static registerFormat(mimeType, impl) { this.impls[mimeType] = impl; } /** * Returns detected MIME type of the given image buffer. Note that for image * formats with support provided by extensions, the extension must be * registered with an I/O class before it can be detected by ImageUtils. */ static getMimeType(buffer) { for (const mimeType in this.impls) if (this.impls[mimeType].match(buffer)) return mimeType; return null; } /** Returns the dimensions of the image. */ static getSize(buffer, mimeType) { if (!this.impls[mimeType]) return null; return this.impls[mimeType].getSize(buffer); } /** * Returns a conservative estimate of the number of channels in the image. For some image * formats, the method may return 4 indicating the possibility of an alpha channel, without * the ability to guarantee that an alpha channel is present. */ static getChannels(buffer, mimeType) { if (!this.impls[mimeType]) return null; return this.impls[mimeType].getChannels(buffer); } /** Returns a conservative estimate of the GPU memory required by this image. */ static getVRAMByteLength(buffer, mimeType) { if (!this.impls[mimeType]) return null; if (this.impls[mimeType].getVRAMByteLength) return this.impls[mimeType].getVRAMByteLength(buffer); let uncompressedBytes = 0; const channels = 4; const resolution = this.getSize(buffer, mimeType); if (!resolution) return null; while (resolution[0] > 1 || resolution[1] > 1) { uncompressedBytes += resolution[0] * resolution[1] * channels; resolution[0] = Math.max(Math.floor(resolution[0] / 2), 1); resolution[1] = Math.max(Math.floor(resolution[1] / 2), 1); } uncompressedBytes += 1 * channels; return uncompressedBytes; } /** Returns the preferred file extension for the given MIME type. */ static mimeTypeToExtension(mimeType) { if (mimeType === "image/jpeg") return "jpg"; return mimeType.split("/").pop(); } /** Returns the MIME type for the given file extension. */ static extensionToMimeType(extension) { if (extension === "jpg") return "image/jpeg"; if (!extension) return ""; return `image/${extension}`; } }; function validateJPEGBuffer(view, i) { if (i > view.byteLength) throw new TypeError("Corrupt JPG, exceeded buffer limits"); if (view.getUint8(i) !== 255) throw new TypeError("Invalid JPG, marker table corrupted"); return view; } //#endregion //#region src/utils/file-utils.ts /** * *Utility class for working with file systems and URI paths.* * * @category Utilities */ var FileUtils = class { /** * Extracts the basename from a file path, e.g. "folder/model.glb" -> "model". * See: {@link HTTPUtils.basename} */ static basename(uri) { const fileName = uri.split(/[\\/]/).pop(); return fileName.substring(0, fileName.lastIndexOf(".")); } /** * Extracts the extension from a file path, e.g. "folder/model.glb" -> "glb". * See: {@link HTTPUtils.extension} */ static extension(uri) { if (uri.startsWith("data:image/")) { const mimeType = uri.match(/data:(image\/\w+)/)[1]; return ImageUtils.mimeTypeToExtension(mimeType); } else if (uri.startsWith("data:model/gltf+json")) return "gltf"; else if (uri.startsWith("data:model/gltf-binary")) return "glb"; else if (uri.startsWith("data:application/")) return "bin"; return uri.split(/[\\/]/).pop().split(/[.]/).pop(); } }; //#endregion //#region ../../node_modules/gl-matrix/esm/common.js var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array; Math.PI / 180; 180 / Math.PI; //#endregion //#region ../../node_modules/gl-matrix/esm/vec3.js /** * 3 Dimensional Vector * @module vec3 */ /** * Creates a new, empty vec3 * * @returns {vec3} a new 3D vector */ function create() { var out = new ARRAY_TYPE(3); if (ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; } return out; } /** * Calculates the length of a vec3 * * @param {ReadonlyVec3} a vector to calculate length of * @returns {Number} length of a */ function length(a) { var x = a[0]; var y = a[1]; var z = a[2]; return Math.sqrt(x * x + y * y + z * z); } /** * Transforms the vec3 with a mat4. * 4th vector component is implicitly '1' * * @param {vec3} out the receiving vector * @param {ReadonlyVec3} a the vector to transform * @param {ReadonlyMat4} m matrix to transform with * @returns {vec3} out */ function transformMat4(out, a, m) { var x = a[0], y = a[1], z = a[2]; var w = m[3] * x + m[7] * y + m[11] * z + m[15]; w = w || 1; 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; } (function() { var vec = create(); return function(a, stride, offset, count, fn, arg) { var i, l; if (!stride) stride = 3; if (!offset) offset = 0; if (count) l = Math.min(count * stride + offset, a.length); else l = a.length; for (i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; vec[2] = a[i + 2]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; a[i + 2] = vec[2]; } return a; }; })(); //#endregion //#region src/utils/get-bounds.ts /** @hidden Implemented in /core for use by /extensions, publicly exported from /functions. */ function getBounds(node) { const resultBounds = createBounds(); const parents = node.propertyType === "Node" ? [node] : node.listChildren(); for (const parent of parents) parent.traverse((node) => { const mesh = node.getMesh(); if (!mesh) return; const meshBounds = getMeshBounds(mesh, node.getWorldMatrix()); if (meshBounds.min.every(isFinite) && meshBounds.max.every(isFinite)) { expandBounds(meshBounds.min, resultBounds); expandBounds(meshBounds.max, resultBounds); } }); return resultBounds; } /** Computes mesh bounds in world space. */ function getMeshBounds(mesh, worldMatrix) { const meshBounds = createBounds(); for (const prim of mesh.listPrimitives()) { const position = prim.getAttribute("POSITION"); const indices = prim.getIndices(); if (!position) continue; let localPos = [ 0, 0, 0 ]; let worldPos = [ 0, 0, 0 ]; for (let i = 0, il = indices ? indices.getCount() : position.getCount(); i < il; i++) { const index = indices ? indices.getScalar(i) : i; localPos = position.getElement(index, localPos); worldPos = transformMat4(worldPos, localPos, worldMatrix); expandBounds(worldPos, meshBounds); } } return meshBounds; } /** Expands bounds of target by given source. */ function expandBounds(point, target) { for (let i = 0; i < 3; i++) { target.min[i] = Math.min(point[i], target.min[i]); target.max[i] = Math.max(point[i], target.max[i]); } } /** Creates new bounds with min=Infinity, max=-Infinity. */ function createBounds() { return { min: [ Infinity, Infinity, Infinity ], max: [ -Infinity, -Infinity, -Infinity ] }; } //#endregion //#region src/utils/http-utils.ts const NULL_DOMAIN = "https://null.example"; /** * *Utility class for working with URLs.* * * @category Utilities */ var HTTPUtils = class { static DEFAULT_INIT = {}; static PROTOCOL_REGEXP = /^[a-zA-Z]+:\/\//; static dirname(path) { const index = path.lastIndexOf("/"); if (index === -1) return "./"; return path.substring(0, index + 1); } /** * Extracts the basename from a URL, e.g. "folder/model.glb" -> "model". * See: {@link FileUtils.basename} */ static basename(uri) { return FileUtils.basename(new URL(uri, NULL_DOMAIN).pathname); } /** * Extracts the extension from a URL, e.g. "folder/model.glb" -> "glb". * See: {@link FileUtils.extension} */ static extension(uri) { return FileUtils.extension(new URL(uri, NULL_DOMAIN).pathname); } static resolve(base, path) { if (!this.isRelativePath(path)) return path; const stack = base.split("/"); const parts = path.split("/"); stack.pop(); for (let i = 0; i < parts.length; i++) { if (parts[i] === ".") continue; if (parts[i] === "..") stack.pop(); else stack.push(parts[i]); } return stack.join("/"); } /** * Returns true for URLs containing a protocol, and false for both * absolute and relative paths. */ static isAbsoluteURL(path) { return this.PROTOCOL_REGEXP.test(path); } /** * Returns true for paths that are declared relative to some unknown base * path. For example, "foo/bar/" is relative both "/foo/bar/" is not. */ static isRelativePath(path) { return !/^(?:[a-zA-Z]+:)?\//.test(path); } }; //#endregion //#region src/utils/is-plain-object.ts function isObject(o) { return Object.prototype.toString.call(o) === "[object Object]"; } function isPlainObject(o) { if (isObject(o) === false) return false; const ctor = o.constructor; if (ctor === void 0) return true; const prot = ctor.prototype; if (isObject(prot) === false) return false; if (Object.hasOwn(prot, "isPrototypeOf") === false) return false; return true; } //#endregion //#region src/utils/logger.ts /** Logger verbosity thresholds. */ let Verbosity = /* @__PURE__ */ function(Verbosity) { /** No events are logged. */ Verbosity[Verbosity["SILENT"] = 4] = "SILENT"; /** Only error events are logged. */ Verbosity[Verbosity["ERROR"] = 3] = "ERROR"; /** Only error and warn events are logged. */ Verbosity[Verbosity["WARN"] = 2] = "WARN"; /** Only error, warn, and info events are logged. (DEFAULT) */ Verbosity[Verbosity["INFO"] = 1] = "INFO"; /** All events are logged. */ Verbosity[Verbosity["DEBUG"] = 0] = "DEBUG"; return Verbosity; }({}); /** * *Logger utility class.* * * @category Utilities */ var Logger = class Logger { verbosity; /** Logger verbosity thresholds. */ static Verbosity = Verbosity; /** Default logger instance. */ static DEFAULT_INSTANCE = new Logger(1); /** Constructs a new Logger instance. */ constructor(verbosity) { this.verbosity = verbosity; } /** Logs an event at level {@link Logger.Verbosity.DEBUG}. */ debug(text) { if (this.verbosity <= 0) console.debug(text); } /** Logs an event at level {@link Logger.Verbosity.INFO}. */ info(text) { if (this.verbosity <= 1) console.info(text); } /** Logs an event at level {@link Logger.Verbosity.WARN}. */ warn(text) { if (this.verbosity <= 2) console.warn(text); } /** Logs an event at level {@link Logger.Verbosity.ERROR}. */ error(text) { if (this.verbosity <= 3) console.error(text); } }; //#endregion //#region ../../node_modules/gl-matrix/esm/mat4.js /** * Calculates the determinant of a mat4 * * @param {ReadonlyMat4} a the source matrix * @returns {Number} determinant of a */ function determinant(a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var b0 = a00 * a11 - a01 * a10; var b1 = a00 * a12 - a02 * a10; var b2 = a01 * a12 - a02 * a11; var b3 = a20 * a31 - a21 * a30; var b4 = a20 * a32 - a22 * a30; var b5 = a21 * a32 - a22 * a31; var b6 = a00 * b5 - a01 * b4 + a02 * b3; var b7 = a10 * b5 - a11 * b4 + a12 * b3; var b8 = a20 * b2 - a21 * b1 + a22 * b0; var b9 = a30 * b2 - a31 * b1 + a32 * b0; return a13 * b6 - a03 * b7 + a33 * b8 - a23 * b9; } /** * Multiplies two mat4s * * @param {mat4} out the receiving matrix * @param {ReadonlyMat4} a the first operand * @param {ReadonlyMat4} b the second operand * @returns {mat4} out */ function multiply(out, a, b) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var 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; } /** * Returns the scaling factor component of a transformation * matrix. If a matrix is built with fromRotationTranslationScale * with a normalized Quaternion parameter, the returned vector will be * the same as the scaling vector * originally supplied. * @param {vec3} out Vector to receive scaling factor component * @param {ReadonlyMat4} mat Matrix to be decomposed (input) * @return {vec3} out */ function getScaling(out, mat) { var m11 = mat[0]; var m12 = mat[1]; var m13 = mat[2]; var m21 = mat[4]; var m22 = mat[5]; var m23 = mat[6]; var m31 = mat[8]; var m32 = mat[9]; var m33 = mat[10]; out[0] = Math.sqrt(m11 * m11 + m12 * m12 + m13 * m13); out[1] = Math.sqrt(m21 * m21 + m22 * m22 + m23 * m23); out[2] = Math.sqrt(m31 * m31 + m32 * m32 + m33 * m33); return out; } /** * 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 {ReadonlyMat4} mat Matrix to be decomposed (input) * @return {quat} out */ function getRotation(out, mat) { var scaling = new ARRAY_TYPE(3); getScaling(scaling, mat); var is1 = 1 / scaling[0]; var is2 = 1 / scaling[1]; var is3 = 1 / scaling[2]; var sm11 = mat[0] * is1; var sm12 = mat[1] * is2; var sm13 = mat[2] * is3; var sm21 = mat[4] * is1; var sm22 = mat[5] * is2; var sm23 = mat[6] * is3; var sm31 = mat[8] * is1; var sm32 = mat[9] * is2; var sm33 = mat[10] * is3; var trace = sm11 + sm22 + sm33; var S = 0; if (trace > 0) { S = Math.sqrt(trace + 1) * 2; out[3] = .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 + sm11 - sm22 - sm33) * 2; out[3] = (sm23 - sm32) / S; out[0] = .25 * S; out[1] = (sm12 + sm21) / S; out[2] = (sm31 + sm13) / S; } else if (sm22 > sm33) { S = Math.sqrt(1 + sm22 - sm11 - sm33) * 2; out[3] = (sm31 - sm13) / S; out[0] = (sm12 + sm21) / S; out[1] = .25 * S; out[2] = (sm23 + sm32) / S; } else { S = Math.sqrt(1 + sm33 - sm11 - sm22) * 2; out[3] = (sm12 - sm21) / S; out[0] = (sm31 + sm13) / S; out[1] = (sm23 + sm32) / S; out[2] = .25 * S; } return out; } //#endregion //#region src/utils/math-utils.ts /** @hidden */ var MathUtils = class MathUtils { static identity(v) { return v; } static eq(a, b, tolerance = 1e-5) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (Math.abs(a[i] - b[i]) > tolerance) return false; return true; } static clamp(value, min, max) { if (value < min) return min; if (value > max) return max; return value; } static decodeNormalizedInt(i, componentType) { switch (componentType) { case 5126: return i; case 5123: return i / 65535; case 5121: return i / 255; case 5122: return Math.max(i / 32767, -1); case 5120: return Math.max(i / 127, -1); default: throw new Error("Invalid component type."); } } static encodeNormalizedInt(f, componentType) { switch (componentType) { case 5126: return f; case 5123: return Math.round(MathUtils.clamp(f, 0, 1) * 65535); case 5121: return Math.round(MathUtils.clamp(f, 0, 1) * 255); case 5122: return Math.round(MathUtils.clamp(f, -1, 1) * 32767); case 5120: return Math.round(MathUtils.clamp(f, -1, 1) * 127); default: throw new Error("Invalid component type."); } } /** * Decompose a mat4 to TRS properties. * * Equivalent to the Matrix4 decompose() method in three.js, and intentionally not using the * gl-matrix version. See: https://github.com/toji/gl-matrix/issues/408 * * @param srcMat Matrix element, to be decomposed to TRS properties. * @param dstTranslation Translation element, to be overwritten. * @param dstRotation Rotation element, to be overwritten. * @param dstScale Scale element, to be overwritten. */ static decompose(srcMat, dstTranslation, dstRotation, dstScale) { let sx = length([ srcMat[0], srcMat[1], srcMat[2] ]); const sy = length([ srcMat[4], srcMat[5], srcMat[6] ]); const sz = length([ srcMat[8], srcMat[9], srcMat[10] ]); if (determinant(srcMat) < 0) sx = -sx; dstTranslation[0] = srcMat[12]; dstTranslation[1] = srcMat[13]; dstTranslation[2] = srcMat[14]; const _m1 = srcMat.slice(); const invSX = 1 / sx; const invSY = 1 / sy; const invSZ = 1 / sz; _m1[0] *= invSX; _m1[1] *= invSX; _m1[2] *= invSX; _m1[4] *= invSY; _m1[5] *= invSY; _m1[6] *= invSY; _m1[8] *= invSZ; _m1[9] *= invSZ; _m1[10] *= invSZ; getRotation(dstRotation, _m1); dstScale[0] = sx; dstScale[1] = sy; dstScale[2] = sz; } /** * Compose TRS properties to a mat4. * * Equivalent to the Matrix4 compose() method in three.js, and intentionally not using the * gl-matrix version. See: https://github.com/toji/gl-matrix/issues/408 * * @param srcTranslation Translation element of matrix. * @param srcRotation Rotation element of matrix. * @param srcScale Scale element of matrix. * @param dstMat Matrix element, to be modified and returned. * @returns dstMat, overwritten to mat4 equivalent of given TRS properties. */ static compose(srcTranslation, srcRotation, srcScale, dstMat) { const te = dstMat; const x = srcRotation[0], y = srcRotation[1], z = srcRotation[2], w = srcRotation[3]; const x2 = x + x, y2 = y + y, z2 = z + z; const xx = x * x2, xy = x * y2, xz = x * z2; const yy = y * y2, yz = y * z2, zz = z * z2; const wx = w * x2, wy = w * y2, wz = w * z2; const sx = srcScale[0], sy = srcScale[1], sz = srcScale[2]; te[0] = (1 - (yy + zz)) * sx; te[1] = (xy + wz) * sx; te[2] = (xz - wy) * sx; te[3] = 0; te[4] = (xy - wz) * sy; te[5] = (1 - (xx + zz)) * sy; te[6] = (yz + wx) * sy; te[7] = 0; te[8] = (xz + wy) * sz; te[9] = (yz - wx) * sz; te[10] = (1 - (xx + yy)) * sz; te[11] = 0; te[12] = srcTranslation[0]; te[13] = srcTranslation[1]; te[14] = srcTranslation[2]; te[15] = 1; return te; } }; //#endregion //#region src/utils/property-utils.ts function equalsRef(refA, refB) { if (!!refA !== !!refB) return false; const a = refA.getChild(); const b = refB.getChild(); return a === b || a.equals(b); } function equalsRefSet(refSetA, refSetB) { if (!!refSetA !== !!refSetB) return false; const refValuesA = refSetA.values(); const refValuesB = refSetB.values(); if (refValuesA.length !== refValuesB.length) return false; for (let i = 0; i < refValuesA.length; i++) { const a = refValuesA[i]; const b = refValuesB[i]; if (a.getChild() === b.getChild()) continue; if (!a.getChild().equals(b.getChild())) return false; } return true; } function equalsRefMap(refMapA, refMapB) { if (!!refMapA !== !!refMapB) return false; const keysA = refMapA.keys(); const keysB = refMapB.keys(); if (keysA.length !== keysB.length) return false; for (const key of keysA) { const refA = refMapA.get(key); const refB = refMapB.get(key); if (!!refA !== !!refB) return false; const a = refA.getChild(); const b = refB.getChild(); if (a === b) continue; if (!a.equals(b)) return false; } return true; } function equalsArray(a, b) { if (a === b) return true; if (!!a !== !!b || !a || !b) return false; if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true; } function equalsObject(_a, _b) { if (_a === _b) return true; if (!!_a !== !!_b) return false; if (!isPlainObject(_a) || !isPlainObject(_b)) return _a === _b; const a = _a; const b = _b; let numKeysA = 0; let numKeysB = 0; let key; for (key in a) numKeysA++; for (key in b) numKeysB++; if (numKeysA !== numKeysB) return false; for (key in a) { const valueA = a[key]; const valueB = b[key]; if (isArray(valueA) && isArray(valueB)) { if (!equalsArray(valueA, valueB)) return false; } else if (isPlainObject(valueA) && isPlainObject(valueB)) { if (!equalsObject(valueA, valueB)) return false; } else if (valueA !== valueB) return false; } return true; } function isArray(value) { return Array.isArray(value) || ArrayBuffer.isView(value); } //#endregion //#region src/utils/uuid.ts const ALPHABET = "23456789abdegjkmnpqrvwxyzABDEGJKMNPQRVWXYZ"; const UNIQUE_RETRIES = 999; const ID_LENGTH = 6; const previousIDs = /* @__PURE__ */ new Set(); const generateOne = function() { let rtn = ""; for (let i = 0; i < ID_LENGTH; i++) rtn += ALPHABET.charAt(Math.floor(Math.random() * 42)); return rtn; }; /** * Short ID generator. * * Generated IDs are short, easy to type, and unique for the duration of the program's execution. * Uniqueness across multiple program executions, or on other devices, is not guaranteed. Based on * [Short ID Generation in JavaScript](https://tomspencer.dev/blog/2014/11/16/short-id-generation-in-javascript/), * with alterations. * * @category Utilities * @hidden */ const uuid = function() { for (let retries = 0; retries < UNIQUE_RETRIES; retries++) { const id = generateOne(); if (!previousIDs.has(id)) { previousIDs.add(id); return id; } } return ""; }; //#endregion //#region src/properties/property.ts const COPY_IDENTITY = (t) => t; const EMPTY_SET = /* @__PURE__ */ new Set(); /** * *Properties represent distinct resources in a glTF asset, referenced by other properties.* * * For example, each material and texture is a property, with material properties holding * references to the textures. All properties are created with factory methods on the * {@link Document} in which they should be constructed. Properties are destroyed by calling * {@link Property.dispose}(). * * Usage: * * ```ts * const texture = doc.createTexture('myTexture'); * doc.listTextures(); // → [texture x 1] * * // Attach a texture to a material. * material.setBaseColorTexture(texture); * material.getBaseColortexture(); // → texture * * // Detaching a texture removes any references to it, except from the doc. * texture.detach(); * material.getBaseColorTexture(); // → null * doc.listTextures(); // → [texture x 1] * * // Disposing a texture removes all references to it, and its own references. * texture.dispose(); * doc.listTextures(); // → [] * ``` * * Reference: * - [glTF → Concepts](https://github.com/KhronosGroup/gltf/blob/main/specification/2.0/README.md#concepts) * * @category Properties */ var Property = class extends GraphNode { /** @hidden */ constructor(graph, name = "") { super(graph); this[$attributes]["name"] = name; this.init(); this.dispatchEvent({ type: "create" }); } /** * Returns the Graph associated with this Property. For internal use. * @hidden * @experimental */ getGraph() { return this.graph; } /** * Returns default attributes for the property. Empty lists and maps should be initialized * to empty arrays and objects. Always invoke `super.getDefaults()` and extend the result. */ getDefaults() { return Object.assign(super.getDefaults(), { name: "", extras: {} }); } /** @hidden */ set(attribute, value) { if (Array.isArray(value)) value = value.slice(); return super.set(attribute, value); } /********************************************************************************************** * Name. */ /** * Returns the name of this property. While names are not required to be unique, this is * encouraged, and non-unique names will be overwritten in some tools. For custom data about * a property, prefer to use Extras. */ getName() { return this.get("name"); } /** * Sets the name of this property. While names are not required to be unique, this is * encouraged, and non-unique names will be overwritten in some tools. For custom data about * a property, prefer to use Extras. */ setName(name) { return this.set("name", name); } /********************************************************************************************** * Extras. */ /** * Returns a reference to the Extras object, containing application-specific data for this * Property. Extras should be an Object, not a primitive value, for best portability. */ getExtras() { return this.get("extras"); } /** * Updates the Extras object, containing application-specific data for this Property. Extras * should be an Object, not a primitive value, for best portability. */ setExtras(extras) { return this.set("extras", extras); } /********************************************************************************************** * Graph state. */ /** * Makes a copy of this property, with the same resources (by reference) as the original. */ clone() { const PropertyClass = this.constructor; return new PropertyClass(this.graph).copy(this, COPY_IDENTITY); } /** * Copies all data from another property to this one. Child properties are copied by reference, * unless a 'resolve' function is given to override that. * @param other Property to copy references from. * @param resolve Function to resolve each Property being transferred. Default is identity. */ copy(other, resolve = COPY_IDENTITY) { for (const key in this[$attributes]) { const value = this[$attributes][key]; if (value instanceof GraphEdge$1) { if (!this[$immutableKeys].has(key)) value.dispose(); } else if (value instanceof RefList$1 || value instanceof RefSet$1) for (const ref of value.values()) ref.dispose(); else if (value instanceof RefMap$1) for (const ref of value.values()) ref.dispose(); } for (const key in other[$attributes]) { const thisValue = this[$attributes][key]; const otherValue = other[$attributes][key]; if (otherValue instanceof GraphEdge$1) if (this[$immutableKeys].has(key)) thisValue.getChild().copy(resolve(otherValue.getChild()), resolve); else this.setRef(key, resolve(otherValue.getChild()), otherValue.getAttributes()); else if (otherValue instanceof RefSet$1 || otherValue instanceof RefList$1) for (const ref of otherValue.values()) this.addRef(key, resolve(ref.getChild()), ref.getAttributes()); else if (otherValue instanceof RefMap$1) for (const subkey of otherValue.keys()) { const ref = otherValue.get(subkey); this.setRefMap(key, subkey, resolve(ref.getChild()), ref.getAttributes()); } else if (isPlainObject(otherValue)) this[$attributes][key] = JSON.parse(JSON.stringify(otherValue)); else if (Array.isArray(otherValue) || otherValue instanceof ArrayBuffer || ArrayBuffer.isView(otherValue)) this[$attributes][key] = otherValue.slice(); else this[$attributes][key] = otherValue; } return this; } /** * Returns true if two properties are deeply equivalent, recursively comparing the attributes * of the properties. Optionally, a 'skip' set may be included, specifying attributes whose * values should not be considered in the comparison. * * Example: Two {@link Primitive Primitives} are equivalent if they have accessors and * materials with equivalent content — but not necessarily the same specific accessors * and materials. */ equals(other, skip = EMPTY_SET) { if (this === other) return true; if (this.propertyType !== other.propertyType) return false; for (const key in this[$attributes]) { if (skip.has(key)) continue; const a = this[$attributes][key]; const b = other[$attributes][key]; if (a instanceof GraphEdge$1 || b instanceof GraphEdge$1) { if (!equalsRef(a, b)) return false; } else if (a instanceof RefSet$1 || b instanceof RefSet$1 || a instanceof RefList$1 || b instanceof RefList$1) { if (!equalsRefSet(a, b)) return false; } else if (a instanceof RefMap$1 || b instanceof RefMap$1) { if (!equalsRefMap(a, b)) return false; } else if (isPlainObject(a) || isPlainObject(b)) { if (!equalsObject(a, b)) return false; } else if (isArray(a) || isArray(b)) { if (!equalsArray(a, b)) return false; } else if (a !== b) return false; } return true; } detach() { this.graph.disconnectParents(this, (n) => n.propertyType !== "Root"); return this; } /** * Returns a list of all properties that hold a reference to this property. For example, a * material may hold references to various textures, but a texture does not hold references * to the materials that use it. * * It is often necessary to filter the results for a particular type: some resources, like * {@link Accessor}s, may be referenced by different types of properties. Most properties * include the {@link Root} as a parent, which is usually not of interest. * * Usage: * * ```ts * const materials = texture * .listParents() * .filter((p) => p instanceof Material) * ``` */ listParents() { return this.graph.listParents(this); } }; //#endregion //#region src/properties/extensible-property.ts /** * *A {@link Property} that can have {@link ExtensionProperty} instances attached.* * * Most properties are extensible. See the {@link Extension} documentation for information about * how to use extensions. * * @category Properties */ var ExtensibleProperty = class extends Property { getDefaults() { return Object.assign(super.getDefaults(), { extensions: new RefMap$1() }); } /** Returns an {@link ExtensionProperty} attached to this Property, if any. */ getExtension(name) { return this.getRefMap("extensions", name); } /** * Attaches the given {@link ExtensionProperty} to this Property. For a given extension, only * one ExtensionProperty may be attached to any one Property at a time. */ setExtension(name, extensionProperty) { if (extensionProperty) extensionProperty._validateParent(this); return this.setRefMap("extensions", name, extensionProperty); } /** Lists all {@link ExtensionProperty} instances attached to this Property. */ listExtensions() { return this.listRefMapValues("extensions"); } }; //#endregion //#region src/properties/accessor.ts /** * *Accessors store lists of numeric, vector, or matrix elements in a typed array.* * * All large data for {@link Mesh}, {@link Skin}, and {@link Animation} properties is stored in * {@link Accessor}s, organized into one or more {@link Buffer}s. Each accessor provides data in * typed arrays, with two abstractions: * * *Elements* are the logical divisions of the data into useful types: `"SCALAR"`, `"VEC2"`, * `"VEC3"`, `"VEC4"`, `"MAT3"`, or `"MAT4"`. The element type can be determined with the * {@link Accessor.getType getType}() method, and the number of elements in the accessor determine its * {@link Accessor.getCount getCount}(). The number of components in an element — e.g. 9 for `"MAT3"` — are its * {@link Accessor.getElementSize getElementSize}(). See {@link Accessor.Type}. * * *Components* are the numeric values within an element — e.g. `.x` and `.y` for `"VEC2"`. Various * component types are available: `BYTE`, `UNSIGNED_BYTE`, `SHORT`, `UNSIGNED_SHORT`, * `UNSIGNED_INT`, and `FLOAT`. The component type can be determined with the * {@link Accessor.getComponentType getComponentType} method, and the number of bytes in each component determine its * {@link Accessor.getComponentSize getComponentSize}. See {@link Accessor.ComponentType}. * * Usage: * * ```typescript * const accessor = doc.createAccessor('myData') * .setArray(new Float32Array([1,2,3,4,5,6,7,8,9,10,11,12])) * .setType(Accessor.Type.VEC3) * .setBuffer(doc.getRoot().listBuffers()[0]); * * accessor.getCount(); // → 4 * accessor.getElementSize(); // → 3 * accessor.getByteLength(); // → 48 * accessor.getElement(1, []); // → [4, 5, 6] * * accessor.setElement(0, [10, 20, 30]); * ``` * * Data access through the {@link Accessor.getElement getElement} and {@link Accessor.setElement setElement} * methods reads or overwrites the content of the underlying typed array. These methods use * element arrays intended to be compatible with the [gl-matrix](https://github.com/toji/gl-matrix) * library, or with the `toArray`/`fromArray` methods of libraries like three.js and babylon.js. * * Each Accessor must be assigned to a {@link Buffer}, which determines where the accessor's data * is stored in the final file. Assigning Accessors to different Buffers allows the data to be * written to different `.bin` files. * * glTF Transform does not expose many details of sparse, normalized, or interleaved accessors * through its API. It reads files using those techniques, presents a simplified view of the data * for editing, and attempts to write data back out with optimizations. For example, vertex * attributes will typically be interleaved by default, regardless of the input file. * * References: * - [glTF → Accessors](https://github.com/KhronosGroup/gltf/blob/main/specification/2.0/README.md#accessors) * * @category Properties */ var Accessor = class Accessor extends ExtensibleProperty { /********************************************************************************************** * Constants. */ /** Element type contained by the accessor (SCALAR, VEC2, ...). */ static Type = { /** Scalar, having 1 value per element. */ SCALAR: "SCALAR", /** 2-component vector, having 2 components per element. */ VEC2: "VEC2", /** 3-component vector, having 3 components per element. */ VEC3: "VEC3", /** 4-component vector, having 4 components per element. */ VEC4: "VEC4", /** 2x2 matrix, having 4 components per element. */ MAT2: "MAT2", /** 3x3 matrix, having 9 components per element. */ MAT3: "MAT3", /** 4x3 matrix, having 16 components per element. */ MAT4: "MAT4" }; /** Data type of the values composing each element in the accessor. */ static ComponentType = { /** * 1-byte signed integer, stored as * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array Int8Array}. */ BYTE: 5120, /** * 1-byte unsigned integer, stored as * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array Uint8Array}. */ UNSIGNED_BYTE: 5121, /** * 2-byte signed integer, stored as * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array Int16Array}. */ SHORT: 5122, /** * 2-byte unsigned integer, stored as * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array Uint16Array}. */ UNSIGNED_SHORT: 5123, /** * 4-byte unsigned integer, stored as * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array Uint32Array}. */ UNSIGNED_INT: 5125, /** * 4-byte floating point number, stored as * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array Float32Array}. */ FLOAT: 5126, /** * 2-byte floating point number, stored as * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float16Array Float16Array}. * Requires {@link KHRAccessorFloat16}. */ FLOAT16: 5131, /** * 8-byte floating point number, stored as * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array Float64Array}. * Requires {@link KHRAccessorFloat64}. */ FLOAT64: 5130 }; /********************************************************************************************** * Instance. */ init() { this.propertyType = "Accessor"; } getDefaults() { return Object.assign(super.getDefaults(), { array: null, type: Accessor.Type.SCALAR, componentType: Accessor.ComponentType.FLOAT, normalized: false, sparse: false, buffer: null }); } /********************************************************************************************** * Static. */ /** Returns size of a given element type, in components. */ static getElementSize(type) { switch (type) { case Accessor.Type.SCALAR: return 1; case Accessor.Type.VEC2: return 2; case Accessor.Type.VEC3: return 3; case Accessor.Type.VEC4: return 4; case Accessor.Type.MAT2: return 4; case Accessor.Type.MAT3: return 9; case Accessor.Type.MAT4: return 16; default: throw new Error("Unexpected type: " + type); } } /** Returns size of a given component type, in bytes. */ static getComponentSize(componentType) { switch (componentType) { case Accessor.ComponentType.BYTE: case Accessor.ComponentType.UNSIGNED_BYTE: return 1; case Accessor.ComponentType.SHORT: case Accessor.ComponentType.UNSIGNED_SHORT: return 2; case Accessor.ComponentType.UNSIGNED_INT: case Accessor.ComponentType.FLOAT: return 4; case Accessor.ComponentType.FLOAT16: return 2; case Accessor.ComponentType.FLOAT64: return 8; default: throw new Error("Unexpected component type: " + componentType); } } /********************************************************************************************** * Min/max bounds. */ /** * Minimum value of each component in this attribute. Unlike in a final glTF file, values * returned by this method will reflect the minimum accounting for {@link .normalized} * state. */ getMinNormalized(target) { const normalized = this.getNormalized(); const elementSize = this.getElementSize(); const componentType = this.getComponentType(); this.getMin(target); if (normalized) for (let j = 0; j < elementSize; j++) target[j] = MathUtils.decodeNormalizedInt(target[j], componentType); return target; } /** * Minimum value of each component in this attribute. Values returned by this method do not * reflect normalization: use {@link .getMinNormalized} in that case. */ getMin(target) { const array = this.getArray(); const count = this.getCount(); const elementSize = this.getElementSize(); for (let j = 0; j < elementSize; j++) target[j] = Infinity; for (let i = 0; i < count * elementSize; i += elementSize) for (let j = 0; j < elementSize; j++) { const value = array[i + j]; if (Number.isFinite(value)) target[j] = Math.min(target[j], value); } return target; } /** * Maximum value of each component in this attribute. Unlike in a final glTF file, values * returned by this method will reflect the minimum accounting for {@link .normalized} * state. */ getMaxNormalized(target) { const normalized = this.getNormalized(); const elementSize = this.getElementSize(); const componentType = this.getComponentType(); this.getMax(target); if (normalized) for (let j = 0; j < elementSize; j++) target[j] = MathUtils.decodeNormalizedInt(target[j], componentType); return target; } /** * Maximum value of each component in this attribute. Values returned by this method do not * reflect normalization: use {@link .getMinNormalized} in that case. */ getMax(target) { const array = this.get("array"); const count = this.getCount(); const elementSize = this.getElementSize(); for (let j = 0; j < elementSize; j++) target[j] = -Infinity; for (let i = 0; i < count * elementSize; i += elementSize) for (let j = 0; j < elementSize; j++) { const value = array[i + j]; if (Number.isFinite(value)) target[j] = Math.max(target[j], value); } return target; } /********************************************************************************************** * Layout. */ /** * Number of elements in the accessor. An array of length 30, containing 10 `VEC3` elements, * will have a count of 10. */ getCount() { const array = this.get("array"); return array ? array.length / this.getElementSize() : 0; } /** Type of element stored in the accessor. `VEC2`, `VEC3`, etc. */ getType() { return this.get("type"); } /** * Sets type of element stored in the accessor. `VEC2`, `VEC3`, etc. Array length must be a * multiple of the component size (`VEC2` = 2, `VEC3` = 3, ...) for the selected type. */ setType(type) { return this.set("type", type); } /** * Number of components in each element of the accessor. Fo