@gltf-transform/core
Version:
glTF 2.0 SDK for JavaScript and TypeScript, on Web and Node.js.
1,490 lines • 210 kB
JavaScript
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