@gltf-transform/core
Version:
glTF 2.0 SDK for JavaScript and TypeScript, on Web and Node.js.
261 lines (226 loc) • 8.33 kB
text/typescript
import type { Graph } from 'property-graph';
import { RefSet } from 'property-graph';
import { type Nullable, PropertyType, VERSION } from '../constants.js';
import type { Extension } from '../extension.js';
import { Accessor } from './accessor.js';
import { Animation } from './animation.js';
import { Buffer } from './buffer.js';
import { Camera } from './camera.js';
import { ExtensibleProperty, type IExtensibleProperty } from './extensible-property.js';
import type { ExtensionProperty } from './extension-property.js';
import { Material } from './material.js';
import { Mesh } from './mesh.js';
import { Node } from './node.js';
import { COPY_IDENTITY, type Property } from './property.js';
import { Scene } from './scene.js';
import { Skin } from './skin.js';
import { Texture } from './texture.js';
interface IAsset {
version: string;
minVersion?: string;
generator?: string;
copyright?: string;
[key: string]: unknown;
}
interface IRoot extends IExtensibleProperty {
asset: IAsset;
defaultScene: Scene;
accessors: RefSet<Accessor>;
animations: RefSet<Animation>;
buffers: RefSet<Buffer>;
cameras: RefSet<Camera>;
materials: RefSet<Material>;
meshes: RefSet<Mesh>;
nodes: RefSet<Node>;
scenes: RefSet<Scene>;
skins: RefSet<Skin>;
textures: RefSet<Texture>;
}
/**
* *Root property of a glTF asset.*
*
* Any properties to be exported with a particular asset must be referenced (directly or
* indirectly) by the root. Metadata about the asset's license, generator, and glTF specification
* version are stored in the asset, accessible with {@link Root.getAsset}.
*
* Properties are added to the root with factory methods on its {@link Document}, and removed by
* calling {@link Property.dispose}() on the resource. Any properties that have been created but
* not disposed will be included when calling the various `root.list*()` methods.
*
* A document's root cannot be removed, and no other root may be created. Unlike other
* {@link Property} types, the `.dispose()`, `.detach()` methods have no useful function on a
* Root property.
*
* Usage:
*
* ```ts
* const root = document.getRoot();
* const scene = document.createScene('myScene');
* const node = document.createNode('myNode');
* scene.addChild(node);
*
* console.log(root.listScenes()); // → [scene x 1]
* ```
*
* Reference: [glTF → Concepts](https://github.com/KhronosGroup/gltf/blob/main/specification/2.0/README.md#concepts)
*
* @category Properties
*/
export class Root extends ExtensibleProperty<IRoot> {
public declare propertyType: PropertyType.ROOT;
private readonly _extensions: Set<Extension> = new Set();
protected init(): void {
this.propertyType = PropertyType.ROOT;
}
protected getDefaults(): Nullable<IRoot> {
return Object.assign(super.getDefaults() as IExtensibleProperty, {
asset: {
generator: `glTF-Transform ${VERSION}`,
version: '2.0',
},
defaultScene: null,
accessors: new RefSet<Accessor>(),
animations: new RefSet<Animation>(),
buffers: new RefSet<Buffer>(),
cameras: new RefSet<Camera>(),
materials: new RefSet<Material>(),
meshes: new RefSet<Mesh>(),
nodes: new RefSet<Node>(),
scenes: new RefSet<Scene>(),
skins: new RefSet<Skin>(),
textures: new RefSet<Texture>(),
});
}
/** @internal */
constructor(graph: Graph<Property>) {
super(graph);
graph.addEventListener('node:create', (event) => {
this._addChildOfRoot(event.target as Property);
});
}
public clone(): this {
throw new Error('Root cannot be cloned.');
}
public copy(other: this, resolve: typeof COPY_IDENTITY = COPY_IDENTITY): this {
// Root cannot be cloned in isolation: only with its Document. Extensions are managed by
// the Document during cloning. The Root, and only the Root, should keep existing
// references while copying to avoid overwriting during a merge.
if (resolve === COPY_IDENTITY) throw new Error('Root cannot be copied.');
// IMPORTANT: Root cannot call super.copy(), which removes existing references.
this.set('asset', { ...other.get('asset') });
this.setName(other.getName());
this.setExtras({ ...other.getExtras() });
this.setDefaultScene(other.getDefaultScene() ? resolve(other.getDefaultScene()!) : null);
for (const extensionName of other.listRefMapKeys('extensions')) {
const otherExtension = other.getExtension(extensionName) as ExtensionProperty;
this.setExtension(extensionName, resolve(otherExtension));
}
return this;
}
private _addChildOfRoot(child: Property): this {
if (child instanceof Scene) {
this.addRef('scenes', child);
} else if (child instanceof Node) {
this.addRef('nodes', child);
} else if (child instanceof Camera) {
this.addRef('cameras', child);
} else if (child instanceof Skin) {
this.addRef('skins', child);
} else if (child instanceof Mesh) {
this.addRef('meshes', child);
} else if (child instanceof Material) {
this.addRef('materials', child);
} else if (child instanceof Texture) {
this.addRef('textures', child);
} else if (child instanceof Animation) {
this.addRef('animations', child);
} else if (child instanceof Accessor) {
this.addRef('accessors', child);
} else if (child instanceof Buffer) {
this.addRef('buffers', child);
}
// No error for untracked property types.
return this;
}
/**
* Returns the `asset` object, which specifies the target glTF version of the asset. Additional
* metadata can be stored in optional properties such as `generator` or `copyright`.
*
* Reference: [glTF → Asset](https://github.com/KhronosGroup/gltf/blob/main/specification/2.0/README.md#asset)
*/
public getAsset(): IAsset {
return this.get('asset');
}
/**********************************************************************************************
* Extensions.
*/
/** Lists all {@link Extension Extensions} enabled for this root. */
public listExtensionsUsed(): Extension[] {
return Array.from(this._extensions);
}
/** Lists all {@link Extension Extensions} enabled and required for this root. */
public listExtensionsRequired(): Extension[] {
return this.listExtensionsUsed().filter((extension) => extension.isRequired());
}
/** @internal */
public _enableExtension(extension: Extension): this {
this._extensions.add(extension);
return this;
}
/** @internal */
public _disableExtension(extension: Extension): this {
this._extensions.delete(extension);
return this;
}
/**********************************************************************************************
* Properties.
*/
/** Lists all {@link Scene} properties associated with this root. */
public listScenes(): Scene[] {
return this.listRefs('scenes');
}
/** Default {@link Scene} associated with this root. */
public setDefaultScene(defaultScene: Scene | null): this {
return this.setRef('defaultScene', defaultScene);
}
/** Default {@link Scene} associated with this root. */
public getDefaultScene(): Scene | null {
return this.getRef('defaultScene');
}
/** Lists all {@link Node} properties associated with this root. */
public listNodes(): Node[] {
return this.listRefs('nodes');
}
/** Lists all {@link Camera} properties associated with this root. */
public listCameras(): Camera[] {
return this.listRefs('cameras');
}
/** Lists all {@link Skin} properties associated with this root. */
public listSkins(): Skin[] {
return this.listRefs('skins');
}
/** Lists all {@link Mesh} properties associated with this root. */
public listMeshes(): Mesh[] {
return this.listRefs('meshes');
}
/** Lists all {@link Material} properties associated with this root. */
public listMaterials(): Material[] {
return this.listRefs('materials');
}
/** Lists all {@link Texture} properties associated with this root. */
public listTextures(): Texture[] {
return this.listRefs('textures');
}
/** Lists all {@link Animation} properties associated with this root. */
public listAnimations(): Animation[] {
return this.listRefs('animations');
}
/** Lists all {@link Accessor} properties associated with this root. */
public listAccessors(): Accessor[] {
return this.listRefs('accessors');
}
/** Lists all {@link Buffer} properties associated with this root. */
public listBuffers(): Buffer[] {
return this.listRefs('buffers');
}
}