Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@gltf-transform/functions

Package Overview
Dependencies
Maintainers
1
Versions
144
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@gltf-transform/functions - npm Package Compare versions

Comparing version 4.0.0-alpha.8 to 4.0.0-alpha.9

5

dist/dequantize.d.ts

@@ -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>;

12

dist/join.d.ts

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc