@gltf-transform/functions
Advanced tools
Comparing version 4.0.0-alpha.8 to 4.0.0-alpha.9
@@ -1,2 +0,2 @@ | ||
import type { Accessor, Primitive, Transform } from '@gltf-transform/core'; | ||
import { type Accessor, type GLTF, type Primitive, type Transform, TypedArray } from '@gltf-transform/core'; | ||
/** Options for the {@link dequantize} function. */ | ||
@@ -44,2 +44,3 @@ export interface DequantizeOptions { | ||
export declare function dequantizePrimitive(prim: Primitive, options: Required<DequantizeOptions>): void; | ||
export declare function dequantizeAttribute(semantic: string, attribute: Accessor, options: Required<DequantizeOptions>): void; | ||
export declare function dequantizeAttribute(attribute: Accessor): void; | ||
export declare function dequantizeAttributeArray(srcArray: TypedArray, componentType: GLTF.AccessorComponentType, normalized: boolean): Float32Array; |
import { Transform } from '@gltf-transform/core'; | ||
/** Options for the {@link flatten} function. */ | ||
export interface FlattenOptions { | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -5,0 +13,0 @@ export declare const FLATTEN_DEFAULTS: Required<FlattenOptions>; |
@@ -10,3 +10,3 @@ import { Transform } from '@gltf-transform/core'; | ||
*/ | ||
keepMeshes: boolean; | ||
keepMeshes?: boolean; | ||
/** | ||
@@ -17,3 +17,11 @@ * Prevents joining _named_ {@link Mesh Meshes} and {@link Node Nodes}. | ||
*/ | ||
keepNamed: boolean; | ||
keepNamed?: boolean; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -20,0 +28,0 @@ export declare const JOIN_DEFAULTS: Required<JoinOptions>; |
@@ -10,2 +10,10 @@ import { Transform } from '@gltf-transform/core'; | ||
min?: number; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -12,0 +20,0 @@ export declare const PALETTE_DEFAULTS: Required<PaletteOptions>; |
@@ -13,2 +13,4 @@ import { Transform } from '@gltf-transform/core'; | ||
keepSolidTextures?: boolean; | ||
/** Whether custom extras should prevent pruning a property. */ | ||
keepExtras?: boolean; | ||
} | ||
@@ -15,0 +17,0 @@ export declare const PRUNE_DEFAULTS: Required<PruneOptions>; |
@@ -24,2 +24,10 @@ import { Transform } from '@gltf-transform/core'; | ||
normalizeWeights?: boolean; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -26,0 +34,0 @@ export declare const QUANTIZE_DEFAULTS: Required<Omit<QuantizeOptions, 'patternTargets'>>; |
@@ -11,2 +11,10 @@ import { Transform } from '@gltf-transform/core'; | ||
target?: 'size' | 'performance'; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -13,0 +21,0 @@ /** |
@@ -6,2 +6,10 @@ import { Transform } from '@gltf-transform/core'; | ||
tolerance?: number; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -8,0 +16,0 @@ /** |
@@ -16,2 +16,10 @@ import { Document, Primitive, Transform } from '@gltf-transform/core'; | ||
lockBorder?: boolean; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -18,0 +26,0 @@ export declare const SIMPLIFY_DEFAULTS: Required<Omit<SimplifyOptions, 'simplifier'>>; |
@@ -1,2 +0,2 @@ | ||
import type { Transform } from '@gltf-transform/core'; | ||
import { Transform } from '@gltf-transform/core'; | ||
/** Options for the {@link unweld} function. */ | ||
@@ -3,0 +3,0 @@ export interface UnweldOptions { |
@@ -57,2 +57,4 @@ import type { NdArray } from 'ndarray'; | ||
export declare function isUsed(prop: Property): boolean; | ||
/** @hidden */ | ||
export declare function isEmptyObject(object: Record<string, unknown>): boolean; | ||
/** | ||
@@ -59,0 +61,0 @@ * Creates a unique key associated with the structure and draw call characteristics of |
@@ -12,2 +12,10 @@ import { Primitive, Transform } from '@gltf-transform/core'; | ||
exhaustive?: boolean; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -14,0 +22,0 @@ export declare const WELD_DEFAULTS: Required<WeldOptions>; |
{ | ||
"name": "@gltf-transform/functions", | ||
"version": "4.0.0-alpha.8", | ||
"version": "4.0.0-alpha.9", | ||
"repository": "github:donmccurdy/glTF-Transform", | ||
@@ -38,4 +38,4 @@ "homepage": "https://gltf-transform.dev/functions.html", | ||
"dependencies": { | ||
"@gltf-transform/core": "^4.0.0-alpha.8", | ||
"@gltf-transform/extensions": "^4.0.0-alpha.8", | ||
"@gltf-transform/core": "^4.0.0-alpha.9", | ||
"@gltf-transform/extensions": "^4.0.0-alpha.9", | ||
"ktx-parse": "^0.6.0", | ||
@@ -56,3 +56,3 @@ "ndarray": "^1.0.19", | ||
}, | ||
"gitHead": "d1267237e48c282dea49abc4d1ca3178853ada58" | ||
"gitHead": "934f1fa4fa5b92a5d851198004e0253bcaa7b71f" | ||
} |
@@ -1,2 +0,10 @@ | ||
import type { Accessor, Document, Primitive, Transform } from '@gltf-transform/core'; | ||
import { | ||
MathUtils, | ||
type Accessor, | ||
type Document, | ||
type GLTF, | ||
type Primitive, | ||
type Transform, | ||
TypedArray, | ||
} from '@gltf-transform/core'; | ||
import { KHRMeshQuantization } from '@gltf-transform/extensions'; | ||
@@ -69,7 +77,12 @@ import { createTransform } from './utils.js'; | ||
for (const semantic of prim.listSemantics()) { | ||
dequantizeAttribute(semantic, prim.getAttribute(semantic)!, options); | ||
if (options.pattern.test(semantic)) { | ||
dequantizeAttribute(prim.getAttribute(semantic)!); | ||
} | ||
} | ||
for (const target of prim.listTargets()) { | ||
for (const semantic of target.listSemantics()) { | ||
dequantizeAttribute(semantic, target.getAttribute(semantic)!, options); | ||
if (options.pattern.test(semantic)) { | ||
dequantizeAttribute(target.getAttribute(semantic)!); | ||
} | ||
} | ||
@@ -79,16 +92,27 @@ } | ||
export function dequantizeAttribute(semantic: string, attribute: Accessor, options: Required<DequantizeOptions>): void { | ||
if (!attribute.getArray()) return; | ||
if (!options.pattern.test(semantic)) return; | ||
if (attribute.getComponentSize() >= 4) return; | ||
export function dequantizeAttribute(attribute: Accessor): void { | ||
const srcArray = attribute.getArray(); | ||
if (!srcArray) return; | ||
const srcArray = attribute.getArray()!; | ||
const dstArray = dequantizeAttributeArray(srcArray, attribute.getComponentType(), attribute.getNormalized()); | ||
attribute.setArray(dstArray).setNormalized(false); | ||
} | ||
export function dequantizeAttributeArray( | ||
srcArray: TypedArray, | ||
componentType: GLTF.AccessorComponentType, | ||
normalized: boolean, | ||
): Float32Array { | ||
const dstArray = new Float32Array(srcArray.length); | ||
for (let i = 0, il = attribute.getCount(), el = [] as number[]; i < il; i++) { | ||
el = attribute.getElement(i, el); | ||
attribute.setArray(dstArray).setElement(i, el).setArray(srcArray); | ||
for (let i = 0, il = srcArray.length; i < il; i++) { | ||
if (normalized) { | ||
dstArray[i] = MathUtils.decodeNormalizedInt(srcArray[i], componentType); | ||
} else { | ||
dstArray[i] = srcArray[i]; | ||
} | ||
} | ||
attribute.setArray(dstArray).setNormalized(false); | ||
return dstArray; | ||
} |
@@ -9,6 +9,16 @@ import { Document, Node, PropertyType, Transform } from '@gltf-transform/core'; | ||
/** Options for the {@link flatten} function. */ | ||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface FlattenOptions {} | ||
export interface FlattenOptions { | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
export const FLATTEN_DEFAULTS: Required<FlattenOptions> = {}; | ||
export const FLATTEN_DEFAULTS: Required<FlattenOptions> = { | ||
cleanup: true, | ||
}; | ||
@@ -35,3 +45,2 @@ /** | ||
export function flatten(_options: FlattenOptions = FLATTEN_DEFAULTS): Transform { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const options = { ...FLATTEN_DEFAULTS, ..._options } as Required<FlattenOptions>; | ||
@@ -95,3 +104,5 @@ | ||
// (5) Clean up leaf nodes. | ||
await document.transform(prune({ propertyTypes: [PropertyType.NODE], keepLeaves: false })); | ||
if (options.cleanup) { | ||
await document.transform(prune({ propertyTypes: [PropertyType.NODE], keepLeaves: false })); | ||
} | ||
@@ -98,0 +109,0 @@ logger.debug(`${NAME}: Complete.`); |
@@ -32,3 +32,2 @@ import { | ||
/** Options for the {@link join} function. */ | ||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface JoinOptions { | ||
@@ -41,3 +40,3 @@ /** | ||
*/ | ||
keepMeshes: boolean; | ||
keepMeshes?: boolean; | ||
/** | ||
@@ -48,3 +47,11 @@ * Prevents joining _named_ {@link Mesh Meshes} and {@link Node Nodes}. | ||
*/ | ||
keepNamed: boolean; | ||
keepNamed?: boolean; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -55,2 +62,3 @@ | ||
keepNamed: false, | ||
cleanup: true, | ||
}; | ||
@@ -99,10 +107,12 @@ | ||
// Clean up. | ||
await document.transform( | ||
prune({ | ||
propertyTypes: [NODE, MESH, PRIMITIVE, ACCESSOR], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
keepLeaves: false, | ||
}), | ||
); | ||
if (options.cleanup) { | ||
await document.transform( | ||
prune({ | ||
propertyTypes: [NODE, MESH, PRIMITIVE, ACCESSOR], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
keepLeaves: false, | ||
}), | ||
); | ||
} | ||
@@ -266,6 +276,4 @@ logger.debug(`${NAME}: Complete.`); | ||
const attribute = prim.getAttribute(semantic); | ||
if (attribute && attribute.getComponentSize() < 4) { | ||
dequantizeAttribute(semantic, attribute, { pattern: /.*/ }); | ||
} | ||
if (attribute) dequantizeAttribute(attribute); | ||
} | ||
} |
@@ -29,2 +29,10 @@ import { | ||
min?: number; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -35,2 +43,3 @@ | ||
min: 5, | ||
cleanup: true, | ||
}; | ||
@@ -296,3 +305,5 @@ | ||
await document.transform(prune({ propertyTypes: [PropertyType.MATERIAL] })); | ||
if (options.cleanup) { | ||
await document.transform(prune({ propertyTypes: [PropertyType.MATERIAL] })); | ||
} | ||
@@ -299,0 +310,0 @@ logger.debug(`${NAME}: Complete.`); |
@@ -29,3 +29,3 @@ import { | ||
import { listTextureSlots } from './list-texture-slots.js'; | ||
import { createTransform } from './utils.js'; | ||
import { createTransform, isEmptyObject } from './utils.js'; | ||
@@ -47,2 +47,4 @@ const NAME = 'prune'; | ||
keepSolidTextures?: boolean; | ||
/** Whether custom extras should prevent pruning a property. */ | ||
keepExtras?: boolean; | ||
} | ||
@@ -68,2 +70,3 @@ | ||
keepSolidTextures: false, | ||
keepExtras: false, | ||
}; | ||
@@ -95,2 +98,3 @@ | ||
const propertyTypes = new Set(options.propertyTypes); | ||
const keepExtras = options.keepExtras; | ||
@@ -104,2 +108,7 @@ return createTransform(NAME, async (document: Document): Promise<void> => { | ||
const onDispose = (event: { target: Property }) => counter.dispose(event.target); | ||
// TODO(cleanup): Publish GraphEvent / GraphEventListener types from 'property-graph'. | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
graph.addEventListener('node:dispose', onDispose as any); | ||
// Prune top-down, so that low-level properties like accessors can be removed if the | ||
@@ -112,3 +121,3 @@ // properties referencing them are removed. | ||
if (mesh.listPrimitives().length > 0) continue; | ||
counter.dispose(mesh); | ||
mesh.dispose(); | ||
} | ||
@@ -120,3 +129,3 @@ } | ||
for (const scene of root.listScenes()) { | ||
nodeTreeShake(graph, scene, counter); | ||
nodeTreeShake(graph, scene, keepExtras); | ||
} | ||
@@ -126,3 +135,3 @@ } | ||
for (const node of root.listNodes()) { | ||
treeShake(node, counter); | ||
treeShake(node, keepExtras); | ||
} | ||
@@ -133,3 +142,3 @@ } | ||
for (const skin of root.listSkins()) { | ||
treeShake(skin, counter); | ||
treeShake(skin, keepExtras); | ||
} | ||
@@ -140,3 +149,3 @@ } | ||
for (const mesh of root.listMeshes()) { | ||
treeShake(mesh, counter); | ||
treeShake(mesh, keepExtras); | ||
} | ||
@@ -147,3 +156,3 @@ } | ||
for (const camera of root.listCameras()) { | ||
treeShake(camera, counter); | ||
treeShake(camera, keepExtras); | ||
} | ||
@@ -153,7 +162,7 @@ } | ||
if (propertyTypes.has(PropertyType.PRIMITIVE)) { | ||
indirectTreeShake(graph, PropertyType.PRIMITIVE, counter); | ||
indirectTreeShake(graph, PropertyType.PRIMITIVE, keepExtras); | ||
} | ||
if (propertyTypes.has(PropertyType.PRIMITIVE_TARGET)) { | ||
indirectTreeShake(graph, PropertyType.PRIMITIVE_TARGET, counter); | ||
indirectTreeShake(graph, PropertyType.PRIMITIVE_TARGET, keepExtras); | ||
} | ||
@@ -200,3 +209,3 @@ | ||
if (!channel.getTargetNode()) { | ||
counter.dispose(channel); | ||
channel.dispose(); | ||
} | ||
@@ -206,6 +215,6 @@ } | ||
const samplers = anim.listSamplers(); | ||
treeShake(anim, counter); | ||
samplers.forEach((sampler) => treeShake(sampler, counter)); | ||
treeShake(anim, keepExtras); | ||
samplers.forEach((sampler) => treeShake(sampler, keepExtras)); | ||
} else { | ||
anim.listSamplers().forEach((sampler) => treeShake(sampler, counter)); | ||
anim.listSamplers().forEach((sampler) => treeShake(sampler, keepExtras)); | ||
} | ||
@@ -216,9 +225,9 @@ } | ||
if (propertyTypes.has(PropertyType.MATERIAL)) { | ||
root.listMaterials().forEach((material) => treeShake(material, counter)); | ||
root.listMaterials().forEach((material) => treeShake(material, keepExtras)); | ||
} | ||
if (propertyTypes.has(PropertyType.TEXTURE)) { | ||
root.listTextures().forEach((texture) => treeShake(texture, counter)); | ||
root.listTextures().forEach((texture) => treeShake(texture, keepExtras)); | ||
if (!options.keepSolidTextures) { | ||
await pruneSolidTextures(document, counter); | ||
await pruneSolidTextures(document); | ||
} | ||
@@ -228,7 +237,7 @@ } | ||
if (propertyTypes.has(PropertyType.ACCESSOR)) { | ||
root.listAccessors().forEach((accessor) => treeShake(accessor, counter)); | ||
root.listAccessors().forEach((accessor) => treeShake(accessor, keepExtras)); | ||
} | ||
if (propertyTypes.has(PropertyType.BUFFER)) { | ||
root.listBuffers().forEach((buffer) => treeShake(buffer, counter)); | ||
root.listBuffers().forEach((buffer) => treeShake(buffer, keepExtras)); | ||
} | ||
@@ -241,2 +250,6 @@ | ||
// TODO(cleanup): Publish GraphEvent / GraphEventListener types from 'property-graph'. | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
graph.removeEventListener('node:dispose', onDispose as any); | ||
if (!counter.empty()) { | ||
@@ -276,3 +289,2 @@ const str = counter | ||
this.disposed[prop.propertyType]++; | ||
prop.dispose(); | ||
} | ||
@@ -290,8 +302,9 @@ } | ||
/** Disposes of the given property if it is unused. */ | ||
function treeShake(prop: Property, counter: DisposeCounter): void { | ||
function treeShake(prop: Property, keepExtras: boolean): void { | ||
// Consider a property unused if it has no references from another property, excluding | ||
// types Root and AnimationChannel. | ||
const parents = prop.listParents().filter((p) => !(p instanceof Root || p instanceof AnimationChannel)); | ||
if (!parents.length) { | ||
counter.dispose(prop); | ||
const needsExtras = keepExtras && !isEmptyObject(prop.getExtras()); | ||
if (!parents.length && !needsExtras) { | ||
prop.dispose(); | ||
} | ||
@@ -305,7 +318,7 @@ } | ||
*/ | ||
function indirectTreeShake(graph: Graph<Property>, propertyType: string, counter: DisposeCounter): void { | ||
function indirectTreeShake(graph: Graph<Property>, propertyType: string, keepExtras: boolean): void { | ||
for (const edge of graph.listEdges()) { | ||
const parent = edge.getParent(); | ||
if (parent.propertyType === propertyType) { | ||
treeShake(parent, counter); | ||
treeShake(parent, keepExtras); | ||
} | ||
@@ -316,4 +329,4 @@ } | ||
/** Iteratively prunes leaf Nodes without contents. */ | ||
function nodeTreeShake(graph: Graph<Property>, prop: Node | Scene, counter: DisposeCounter): void { | ||
prop.listChildren().forEach((child) => nodeTreeShake(graph, child, counter)); | ||
function nodeTreeShake(graph: Graph<Property>, prop: Node | Scene, keepExtras: boolean): void { | ||
prop.listChildren().forEach((child) => nodeTreeShake(graph, child, keepExtras)); | ||
@@ -327,4 +340,5 @@ if (prop instanceof Scene) return; | ||
const isEmpty = graph.listChildren(prop).length === 0; | ||
if (isEmpty && !isUsed) { | ||
counter.dispose(prop); | ||
const needsExtras = keepExtras && !isEmptyObject(prop.getExtras()); | ||
if (isEmpty && !isUsed && !needsExtras) { | ||
prop.dispose(); | ||
} | ||
@@ -483,3 +497,3 @@ } | ||
async function pruneSolidTextures(document: Document, counter: DisposeCounter): Promise<void> { | ||
async function pruneSolidTextures(document: Document): Promise<void> { | ||
const root = document.getRoot(); | ||
@@ -510,3 +524,3 @@ const graph = document.getGraph(); | ||
if (texture.listParents().length === 1) { | ||
counter.dispose(texture); | ||
texture.dispose(); | ||
logger.debug(`${NAME}: Removed solid-color texture "${name}" (${size}px ${slots.join(', ')})`); | ||
@@ -513,0 +527,0 @@ } |
@@ -63,2 +63,10 @@ import { | ||
normalizeWeights?: boolean; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -76,2 +84,3 @@ | ||
normalizeWeights: true, | ||
cleanup: true, | ||
}; | ||
@@ -129,15 +138,17 @@ | ||
await doc.transform( | ||
prune({ | ||
propertyTypes: [PropertyType.ACCESSOR, PropertyType.SKIN, PropertyType.MATERIAL], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
keepLeaves: true, | ||
keepSolidTextures: true, | ||
}), | ||
dedup({ | ||
propertyTypes: [PropertyType.ACCESSOR, PropertyType.MATERIAL, PropertyType.SKIN], | ||
keepUniqueNames: true, | ||
}), | ||
); | ||
if (options.cleanup) { | ||
await doc.transform( | ||
prune({ | ||
propertyTypes: [PropertyType.ACCESSOR, PropertyType.SKIN, PropertyType.MATERIAL], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
keepLeaves: true, | ||
keepSolidTextures: true, | ||
}), | ||
dedup({ | ||
propertyTypes: [PropertyType.ACCESSOR, PropertyType.MATERIAL, PropertyType.SKIN], | ||
keepUniqueNames: true, | ||
}), | ||
); | ||
} | ||
@@ -144,0 +155,0 @@ logger.debug(`${NAME}: Complete.`); |
@@ -17,2 +17,10 @@ import { Accessor, Document, GLTF, Primitive, PropertyType, Transform } from '@gltf-transform/core'; | ||
target?: 'size' | 'performance'; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -22,2 +30,3 @@ | ||
target: 'size', | ||
cleanup: true, | ||
}; | ||
@@ -97,9 +106,11 @@ | ||
// Clean up any attributes left unused by earlier cloning. | ||
await document.transform( | ||
prune({ | ||
propertyTypes: [PropertyType.ACCESSOR], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
}), | ||
); | ||
if (options.cleanup) { | ||
await document.transform( | ||
prune({ | ||
propertyTypes: [PropertyType.ACCESSOR], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
}), | ||
); | ||
} | ||
@@ -106,0 +117,0 @@ if (!plan.indicesToAttributes.size) { |
@@ -11,7 +11,6 @@ import { | ||
Transform, | ||
TransformContext, | ||
TypedArray, | ||
} from '@gltf-transform/core'; | ||
import { dedup } from './dedup.js'; | ||
import { createTransform, isTransformPending } from './utils.js'; | ||
import { createTransform } from './utils.js'; | ||
import { resampleDebug } from 'keyframe-resample'; | ||
@@ -27,2 +26,10 @@ | ||
tolerance?: number; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -34,2 +41,3 @@ | ||
tolerance: 1e-4, | ||
cleanup: true, | ||
}; | ||
@@ -68,3 +76,3 @@ | ||
return createTransform(NAME, async (document: Document, context?: TransformContext): Promise<void> => { | ||
return createTransform(NAME, async (document: Document): Promise<void> => { | ||
const accessorsVisited = new Set<Accessor>(); | ||
@@ -157,3 +165,3 @@ const srcAccessorCount = document.getRoot().listAccessors().length; | ||
const dstAccessorCount = document.getRoot().listAccessors().length; | ||
if (dstAccessorCount > srcAccessorCount && !isTransformPending(context, NAME, 'dedup')) { | ||
if (dstAccessorCount > srcAccessorCount && options.cleanup) { | ||
await document.transform(dedup({ propertyTypes: [PropertyType.ACCESSOR] })); | ||
@@ -160,0 +168,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { Accessor, Document, Primitive, PropertyType, Transform, TransformContext } from '@gltf-transform/core'; | ||
import { Document, Primitive, PropertyType, Transform } from '@gltf-transform/core'; | ||
import { | ||
@@ -8,3 +8,2 @@ createTransform, | ||
deepSwapAttribute, | ||
isTransformPending, | ||
shallowCloneAccessor, | ||
@@ -16,2 +15,4 @@ } from './utils.js'; | ||
import { prune } from './prune.js'; | ||
import { dequantizeAttributeArray } from './dequantize.js'; | ||
import { unweldPrimitive } from './unweld.js'; | ||
@@ -34,2 +35,10 @@ const NAME = 'simplify'; | ||
lockBorder?: boolean; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -41,2 +50,3 @@ | ||
lockBorder: false, | ||
cleanup: true, | ||
}; | ||
@@ -86,7 +96,7 @@ | ||
return createTransform(NAME, async (document: Document, context?: TransformContext): Promise<void> => { | ||
return createTransform(NAME, async (document: Document): Promise<void> => { | ||
const logger = document.getLogger(); | ||
await simplifier.ready; | ||
await document.transform(weld({ overwrite: false })); | ||
await document.transform(weld({ overwrite: false, cleanup: options.cleanup })); | ||
@@ -96,11 +106,11 @@ // Simplify mesh primitives. | ||
for (const prim of mesh.listPrimitives()) { | ||
if (prim.getMode() !== Primitive.Mode.TRIANGLES) { | ||
logger.warn( | ||
`${NAME}: Skipping primitive of mesh "${mesh.getName()}": Requires TRIANGLES draw mode.`, | ||
); | ||
continue; | ||
if (prim.getMode() === Primitive.Mode.TRIANGLES) { | ||
simplifyPrimitive(document, prim, options); | ||
if (prim.getIndices()!.getCount() === 0) prim.dispose(); | ||
} else if (prim.getMode() === Primitive.Mode.POINTS && !!simplifier.simplifyPoints) { | ||
simplifyPrimitive(document, prim, options); | ||
if (prim.getAttribute('POSITION')!.getCount() === 0) prim.dispose(); | ||
} else { | ||
logger.warn(`${NAME}: Skipping primitive of mesh "${mesh.getName()}": Unsupported draw mode.`); | ||
} | ||
simplifyPrimitive(document, prim, options); | ||
if (prim.getIndices()!.getCount() === 0) prim.dispose(); | ||
} | ||
@@ -112,15 +122,12 @@ | ||
// Where simplification removes meshes, we may need to prune leaf nodes. | ||
await document.transform( | ||
prune({ | ||
propertyTypes: [PropertyType.ACCESSOR, PropertyType.NODE], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
keepLeaves: false, | ||
}), | ||
); | ||
// Where multiple primitive indices point into the same vertex streams, simplification | ||
// may write duplicate streams. Find and remove the duplicates after processing. | ||
if (!isTransformPending(context, NAME, 'dedup')) { | ||
await document.transform(dedup({ propertyTypes: [PropertyType.ACCESSOR] })); | ||
if (options.cleanup) { | ||
await document.transform( | ||
prune({ | ||
propertyTypes: [PropertyType.ACCESSOR, PropertyType.NODE], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
keepLeaves: false, | ||
}), | ||
dedup({ propertyTypes: [PropertyType.ACCESSOR] }), | ||
); | ||
} | ||
@@ -136,2 +143,6 @@ | ||
if (prim.getMode() === Primitive.Mode.POINTS) { | ||
return _simplifyPoints(document, prim, options); | ||
} | ||
const logger = document.getLogger(); | ||
@@ -148,20 +159,6 @@ const position = prim.getAttribute('POSITION')!; | ||
if (position.getComponentType() !== Accessor.ComponentType.FLOAT) { | ||
if (position.getNormalized()) { | ||
const src = positionArray; | ||
const dst = new Float32Array(src.length); | ||
// Dequantize. | ||
for (let i = 0, il = position.getCount(), el = [] as number[]; i < il; i++) { | ||
el = position.getElement(i, el); | ||
position.setArray(dst).setElement(i, el).setArray(src); | ||
} | ||
positionArray = dst; | ||
} else { | ||
positionArray = new Float32Array(positionArray); | ||
} | ||
if (!(positionArray instanceof Float32Array)) { | ||
positionArray = dequantizeAttributeArray(positionArray, position.getComponentType(), position.getNormalized()); | ||
} | ||
if (srcIndices.getComponentType() !== Accessor.ComponentType.UNSIGNED_INT) { | ||
if (!(indicesArray instanceof Uint32Array)) { | ||
indicesArray = new Uint32Array(indicesArray); | ||
@@ -173,9 +170,11 @@ } | ||
const targetCount = Math.floor((options.ratio * srcIndexCount) / 3) * 3; | ||
const flags = options.lockBorder ? ['LockBorder'] : []; | ||
const [dstIndicesArray, error] = simplifier.simplify( | ||
indicesArray as Uint32Array, | ||
positionArray as Float32Array, | ||
indicesArray, | ||
positionArray, | ||
3, | ||
targetCount, | ||
options.error, | ||
options.lockBorder ? ['LockBorder'] : [], | ||
flags as 'LockBorder'[], | ||
); | ||
@@ -205,1 +204,48 @@ | ||
} | ||
function _simplifyPoints(document: Document, prim: Primitive, options: Required<SimplifyOptions>): Primitive { | ||
const simplifier = options.simplifier as typeof MeshoptSimplifier; | ||
const logger = document.getLogger(); | ||
const indices = prim.getIndices(); | ||
if (indices) unweldPrimitive(prim); | ||
const position = prim.getAttribute('POSITION')!; | ||
const color = prim.getAttribute('COLOR_0'); | ||
const srcVertexCount = position.getCount(); | ||
let positionArray = position.getArray()!; | ||
let colorArray = color ? color.getArray()! : undefined; | ||
const colorStride = color ? color.getComponentSize() : undefined; | ||
// (1) Gather attributes in Meshopt-compatible format. | ||
if (!(positionArray instanceof Float32Array)) { | ||
positionArray = dequantizeAttributeArray(positionArray, position.getComponentType(), position.getNormalized()); | ||
} | ||
if (colorArray && !(colorArray instanceof Float32Array)) { | ||
colorArray = dequantizeAttributeArray(colorArray, position.getComponentType(), position.getNormalized()); | ||
} | ||
// (2) Run simplification. | ||
simplifier.useExperimentalFeatures = true; | ||
const targetCount = Math.floor(options.ratio * srcVertexCount); | ||
const dstIndicesArray = simplifier.simplifyPoints(positionArray, 3, targetCount, colorArray, colorStride); | ||
simplifier.useExperimentalFeatures = false; | ||
// (3) Write vertex attributes. | ||
const [remap, unique] = simplifier.compactMesh(dstIndicesArray); | ||
logger.debug(`${NAME}: ${formatDeltaOp(position.getCount(), unique)} vertices.`); | ||
for (const srcAttribute of deepListAttributes(prim)) { | ||
const dstAttribute = shallowCloneAccessor(document, srcAttribute); | ||
remapAttribute(dstAttribute, remap, unique); | ||
deepSwapAttribute(prim, srcAttribute, dstAttribute); | ||
if (srcAttribute.listParents().length === 1) srcAttribute.dispose(); | ||
} | ||
return prim; | ||
} |
@@ -1,2 +0,2 @@ | ||
import type { Accessor, Document, ILogger, Transform, TypedArray } from '@gltf-transform/core'; | ||
import { Accessor, Document, ILogger, Primitive, Transform, TypedArray } from '@gltf-transform/core'; | ||
import { createTransform, formatDeltaOp } from './utils.js'; | ||
@@ -31,36 +31,48 @@ | ||
for (const prim of mesh.listPrimitives()) { | ||
const indices = prim.getIndices(); | ||
if (!indices) continue; | ||
unweldPrimitive(prim, visited); | ||
} | ||
} | ||
const srcVertexCount = prim.getAttribute('POSITION')!.getCount(); | ||
logger.debug(`${NAME}: Complete.`); | ||
}); | ||
} | ||
// Vertex attributes. | ||
for (const srcAttribute of prim.listAttributes()) { | ||
prim.swap(srcAttribute, unweldAttribute(srcAttribute, indices, logger, visited)); | ||
/** | ||
* @hidden | ||
* @internal | ||
*/ | ||
export function unweldPrimitive(prim: Primitive, visited = new Map<Accessor, Map<Accessor, Accessor>>()): void { | ||
const indices = prim.getIndices(); | ||
if (!indices) return; | ||
// Clean up. | ||
if (srcAttribute.listParents().length === 1) srcAttribute.dispose(); | ||
} | ||
const graph = prim.getGraph(); | ||
const document = Document.fromGraph(graph)!; | ||
const logger = document.getLogger(); | ||
// Morph target vertex attributes. | ||
for (const target of prim.listTargets()) { | ||
for (const srcAttribute of target.listAttributes()) { | ||
target.swap(srcAttribute, unweldAttribute(srcAttribute, indices, logger, visited)); | ||
const srcVertexCount = prim.getAttribute('POSITION')!.getCount(); | ||
// Clean up. | ||
if (srcAttribute.listParents().length === 1) srcAttribute.dispose(); | ||
} | ||
} | ||
// Vertex attributes. | ||
for (const srcAttribute of prim.listAttributes()) { | ||
prim.swap(srcAttribute, unweldAttribute(srcAttribute, indices, logger, visited)); | ||
const dstVertexCount = prim.getAttribute('POSITION')!.getCount(); | ||
logger.debug(`${NAME}: ${formatDeltaOp(srcVertexCount, dstVertexCount)} vertices.`); | ||
// Clean up. | ||
if (srcAttribute.listParents().length === 1) srcAttribute.dispose(); | ||
} | ||
// Clean up. | ||
prim.setIndices(null); | ||
if (indices.listParents().length === 1) indices.dispose(); | ||
} | ||
// Morph target vertex attributes. | ||
for (const target of prim.listTargets()) { | ||
for (const srcAttribute of target.listAttributes()) { | ||
target.swap(srcAttribute, unweldAttribute(srcAttribute, indices, logger, visited)); | ||
// Clean up. | ||
if (srcAttribute.listParents().length === 1) srcAttribute.dispose(); | ||
} | ||
} | ||
logger.debug(`${NAME}: Complete.`); | ||
}); | ||
const dstVertexCount = prim.getAttribute('POSITION')!.getCount(); | ||
logger.debug(`${NAME}: ${formatDeltaOp(srcVertexCount, dstVertexCount)} vertices.`); | ||
// Clean up. | ||
prim.setIndices(null); | ||
if (indices.listParents().length === 1) indices.dispose(); | ||
} | ||
@@ -67,0 +79,0 @@ |
@@ -300,2 +300,8 @@ import type { NdArray } from 'ndarray'; | ||
/** @hidden */ | ||
export function isEmptyObject(object: Record<string, unknown>): boolean { | ||
for (const key in object) return false; | ||
return true; | ||
} | ||
/** | ||
@@ -302,0 +308,0 @@ * Creates a unique key associated with the structure and draw call characteristics of |
@@ -76,2 +76,10 @@ import { Accessor, BufferUtils, Document, Primitive, PropertyType, Transform, vec3 } from '@gltf-transform/core'; | ||
exhaustive?: boolean; | ||
/** | ||
* Whether to perform cleanup steps after completing the operation. Recommended, and enabled by | ||
* default. Cleanup removes temporary resources created during the operation, but may also remove | ||
* pre-existing unused or duplicate resources in the {@link Document}. Applications that require | ||
* keeping these resources may need to disable cleanup, instead calling {@link dedup} and | ||
* {@link prune} manually (with customized options) later in the processing pipeline. | ||
*/ | ||
cleanup?: boolean; | ||
} | ||
@@ -84,2 +92,3 @@ | ||
exhaustive: false, // donmccurdy/glTF-Transform#886 | ||
cleanup: true, | ||
}; | ||
@@ -137,11 +146,13 @@ | ||
// Welding removes degenerate meshes; prune leaf nodes afterward. | ||
await doc.transform( | ||
prune({ | ||
propertyTypes: [PropertyType.ACCESSOR, PropertyType.NODE], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
keepLeaves: false, | ||
}), | ||
dedup({ propertyTypes: [PropertyType.ACCESSOR] }), | ||
); | ||
if (options.cleanup) { | ||
await doc.transform( | ||
prune({ | ||
propertyTypes: [PropertyType.ACCESSOR, PropertyType.NODE], | ||
keepAttributes: true, | ||
keepIndices: true, | ||
keepLeaves: false, | ||
}), | ||
dedup({ propertyTypes: [PropertyType.ACCESSOR] }), | ||
); | ||
} | ||
@@ -148,0 +159,0 @@ logger.debug(`${NAME}: Complete.`); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2191531
25830