UNPKG

@gltf-transform/core

Version:

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

284 lines (253 loc) 8.88 kB
import { Graph } from 'property-graph'; import type { Extension } from './extension.js'; import { Accessor, Animation, AnimationChannel, AnimationSampler, Buffer, Camera, Material, Mesh, Node, Primitive, PrimitiveTarget, type Property, Root, Scene, Skin, Texture, } from './properties/index.js'; import { type ILogger, Logger } from './utils/index.js'; export interface TransformContext { stack: string[]; } export type Transform = (doc: Document, context?: TransformContext) => void; /** * *Wraps a glTF asset and its resources for easier modification.* * * Documents manage glTF assets and the relationships among dependencies. The document wrapper * allow tools to read and write changes without dealing with array indices or byte offsets, which * would otherwise require careful management over the course of a file modification. An internal * graph structure allows any property in the glTF file to maintain references to its dependencies, * and makes it easy to determine where a particular property dependency is being used. For * example, finding a list of materials that use a particular texture is as simple as calling * {@link Texture.listParents}(). * * A new resource {@link Property} (e.g. a {@link Mesh} or {@link Material}) is created by calling * 'create' methods on the document. Resources are destroyed by calling {@link Property.dispose}(). * * ```ts * import fs from 'fs/promises'; * import { Document } from '@gltf-transform/core'; * import { dedup } from '@gltf-transform/functions'; * * const document = new Document(); * * const texture1 = document.createTexture('myTexture') * .setImage(await fs.readFile('path/to/image.png')) * .setMimeType('image/png'); * const texture2 = document.createTexture('myTexture2') * .setImage(await fs.readFile('path/to/image2.png')) * .setMimeType('image/png'); * * // Document containing duplicate copies of the same texture. * document.getRoot().listTextures(); // → [texture x 2] * * await document.transform( * dedup({textures: true}), * // ... * ); * * // Document with duplicate textures removed. * document.getRoot().listTextures(); // → [texture x 1] * ``` * * Reference: * - [glTF → Basics](https://github.com/KhronosGroup/gltf/blob/main/specification/2.0/README.md#gltf-basics) * - [glTF → Concepts](https://github.com/KhronosGroup/gltf/blob/main/specification/2.0/README.md#concepts) * * @category Documents */ export class Document { private _graph: Graph<Property> = new Graph<Property>(); private _root: Root = new Root(this._graph); private _logger: ILogger = Logger.DEFAULT_INSTANCE; /** * Enables lookup of a Document from its Graph. For internal use, only. * @internal * @experimental */ private static _GRAPH_DOCUMENTS = new WeakMap<Graph<Property>, Document>(); /** * Returns the Document associated with a given Graph, if any. * @hidden * @experimental */ public static fromGraph(graph: Graph<Property>): Document | null { return Document._GRAPH_DOCUMENTS.get(graph) || null; } /** Creates a new Document, representing an empty glTF asset. */ public constructor() { Document._GRAPH_DOCUMENTS.set(this._graph, this); } /** Returns the glTF {@link Root} property. */ public getRoot(): Root { return this._root; } /** * Returns the {@link Graph} representing connectivity of resources within this document. * @hidden */ public getGraph(): Graph<Property> { return this._graph; } /** Returns the {@link Logger} instance used for any operations performed on this document. */ public getLogger(): ILogger { return this._logger; } /** * Overrides the {@link Logger} instance used for any operations performed on this document. * * Usage: * * ```ts * doc * .setLogger(new Logger(Logger.Verbosity.SILENT)) * .transform(dedup(), weld()); * ``` */ public setLogger(logger: ILogger): Document { this._logger = logger; return this; } /** * Clones this Document, copying all resources within it. * @deprecated Use 'cloneDocument(document)' from '@gltf-transform/functions'. * @hidden * @internal */ public clone(): Document { throw new Error(`Use 'cloneDocument(source)' from '@gltf-transform/functions'.`); } /** * Merges the content of another Document into this one, without affecting the original. * @deprecated Use 'mergeDocuments(target, source)' from '@gltf-transform/functions'. * @hidden * @internal */ public merge(_other: Document): this { throw new Error(`Use 'mergeDocuments(target, source)' from '@gltf-transform/functions'.`); } /** * Applies a series of modifications to this document. Each transformation is asynchronous, * takes the {@link Document} as input, and returns nothing. Transforms are applied in the * order given, which may affect the final result. * * Usage: * * ```ts * await doc.transform( * dedup(), * prune() * ); * ``` * * @param transforms List of synchronous transformation functions to apply. */ public async transform(...transforms: Transform[]): Promise<this> { const stack = transforms.map((fn) => fn.name); for (const transform of transforms) { await transform(this, { stack }); } return this; } /********************************************************************************************** * Extension factory method. */ /** * Creates a new {@link Extension}, for the extension type of the given constructor. If the * extension is already enabled for this Document, the previous Extension reference is reused. */ createExtension<T extends Extension>(ctor: new (doc: Document) => T): T { const extensionName = (ctor as unknown as { EXTENSION_NAME: 'string' }).EXTENSION_NAME; const prevExtension = this.getRoot() .listExtensionsUsed() .find((ext) => ext.extensionName === extensionName); return (prevExtension || new ctor(this)) as T; } /********************************************************************************************** * Property factory methods. */ /** Creates a new {@link Scene} attached to this document's {@link Root}. */ createScene(name = ''): Scene { return new Scene(this._graph, name); } /** Creates a new {@link Node} attached to this document's {@link Root}. */ createNode(name = ''): Node { return new Node(this._graph, name); } /** Creates a new {@link Camera} attached to this document's {@link Root}. */ createCamera(name = ''): Camera { return new Camera(this._graph, name); } /** Creates a new {@link Skin} attached to this document's {@link Root}. */ createSkin(name = ''): Skin { return new Skin(this._graph, name); } /** Creates a new {@link Mesh} attached to this document's {@link Root}. */ createMesh(name = ''): Mesh { return new Mesh(this._graph, name); } /** * Creates a new {@link Primitive}. Primitives must be attached to a {@link Mesh} * for use and export; they are not otherwise associated with a {@link Root}. */ createPrimitive(): Primitive { return new Primitive(this._graph); } /** * Creates a new {@link PrimitiveTarget}, or morph target. Targets must be attached to a * {@link Primitive} for use and export; they are not otherwise associated with a {@link Root}. */ createPrimitiveTarget(name = ''): PrimitiveTarget { return new PrimitiveTarget(this._graph, name); } /** Creates a new {@link Material} attached to this document's {@link Root}. */ createMaterial(name = ''): Material { return new Material(this._graph, name); } /** Creates a new {@link Texture} attached to this document's {@link Root}. */ createTexture(name = ''): Texture { return new Texture(this._graph, name); } /** Creates a new {@link Animation} attached to this document's {@link Root}. */ createAnimation(name = ''): Animation { return new Animation(this._graph, name); } /** * Creates a new {@link AnimationChannel}. Channels must be attached to an {@link Animation} * for use and export; they are not otherwise associated with a {@link Root}. */ createAnimationChannel(name = ''): AnimationChannel { return new AnimationChannel(this._graph, name); } /** * Creates a new {@link AnimationSampler}. Samplers must be attached to an {@link Animation} * for use and export; they are not otherwise associated with a {@link Root}. */ createAnimationSampler(name = ''): AnimationSampler { return new AnimationSampler(this._graph, name); } /** Creates a new {@link Accessor} attached to this document's {@link Root}. */ createAccessor(name = '', buffer: Buffer | null = null): Accessor { if (!buffer) { buffer = this.getRoot().listBuffers()[0]; } return new Accessor(this._graph, name).setBuffer(buffer); } /** Creates a new {@link Buffer} attached to this document's {@link Root}. */ createBuffer(name = ''): Buffer { return new Buffer(this._graph, name); } }