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.0 to 4.0.0-alpha.1

./dist/functions.cjs

2

dist/center.d.ts

@@ -16,3 +16,5 @@ import type { Transform, vec3 } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function center(_options?: CenterOptions): Transform;

@@ -0,0 +0,0 @@ import type { Node } from '@gltf-transform/core';

@@ -0,0 +0,0 @@ import { Node } from '@gltf-transform/core';

@@ -21,3 +21,5 @@ import { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function dedup(_options?: DedupOptions): Transform;

@@ -14,3 +14,5 @@ import type { Transform } from '@gltf-transform/core';

* necessary for compatibility with applications that don't support quantization.
*
* @category Transforms
*/
export declare function dequantize(_options?: DequantizeOptions): Transform;

@@ -19,3 +19,5 @@ import type { Transform } from '@gltf-transform/core';

* This function is a thin wrapper around the {@link KHRDracoMeshCompression} extension itself.
*
* @category Transforms
*/
export declare function draco(_options?: DracoOptions): Transform;

@@ -22,3 +22,5 @@ import { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function flatten(_options?: FlattenOptions): Transform;
import type { Node, Scene } from '@gltf-transform/core';
/** @deprecated Use {@link listNodeScenes} instead. */
export declare function getNodeScene(node: Node): Scene | null;

@@ -60,2 +60,3 @@ /**

export * from './get-node-scene.js';
export * from './get-texture-color-space.js';
export * from './inspect.js';

@@ -72,2 +73,3 @@ export * from './instance.js';

export * from './normals.js';
export * from './palette.js';
export * from './partition.js';

@@ -74,0 +76,0 @@ export * from './prune.js';

@@ -0,0 +0,0 @@ import { Document, GLTF } from '@gltf-transform/core';

@@ -22,3 +22,5 @@ import { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function instance(_options?: InstanceOptions): Transform;

@@ -0,0 +0,0 @@ import { Primitive } from '@gltf-transform/core';

@@ -43,3 +43,5 @@ import { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function join(_options?: JoinOptions): Transform;

@@ -0,0 +0,0 @@ import { Node, Scene } from '@gltf-transform/core';

@@ -16,2 +16,3 @@ import { Texture } from '@gltf-transform/core';

* }
* ```
*/

@@ -18,0 +19,0 @@ export declare function listTextureChannels(texture: Texture): TextureChannel[];

@@ -1,8 +0,11 @@

import { Texture, TextureInfo } from '@gltf-transform/core';
import { Material, Texture, TextureInfo } from '@gltf-transform/core';
/**
* Lists all {@link TextureInfo} definitions associated with a given {@link Texture}.
* Lists all {@link TextureInfo} definitions associated with a given
* {@link Texture}. May be used to determine which UV transforms
* and texCoord indices are applied to the material, without explicitly
* checking the material properties and extensions.
*
* Example:
*
* ```js
* ```typescript
* // Find TextureInfo instances associated with the texture.

@@ -13,5 +16,21 @@ * const results = listTextureInfo(texture);

* const texCoords = results.map((info) => info.getTexCoord());
* // → [0, 0, 1]
* // → [0, 1]
* ```
*/
export declare function listTextureInfo(texture: Texture): TextureInfo[];
/**
* Lists all {@link TextureInfo} definitions associated with any {@link Texture}
* on the given {@link Material}. May be used to determine which UV transforms
* and texCoord indices are applied to the material, without explicitly
* checking the material properties and extensions.
*
* Example:
*
* ```typescript
* const results = listTextureInfoByMaterial(material);
*
* const texCoords = results.map((info) => info.getTexCoord());
* // → [0, 1]
* ```
*/
export declare function listTextureInfoByMaterial(material: Material): TextureInfo[];

@@ -0,0 +0,0 @@ import { Texture } from '@gltf-transform/core';

@@ -29,3 +29,5 @@ import type { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function meshopt(_options: MeshoptOptions): Transform;

@@ -11,3 +11,5 @@ import type { Transform } from '@gltf-transform/core';

* No options are currently implemented for this function.
*
* @category Transforms
*/
export declare function metalRough(_options?: MetalRoughOptions): Transform;

@@ -17,3 +17,5 @@ import type { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function normals(_options?: NormalsOptions): Transform;

@@ -20,3 +20,5 @@ import { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function partition(_options?: PartitionOptions): Transform;

@@ -26,3 +26,5 @@ import { Transform } from '@gltf-transform/core';

* No options are currently implemented for this function.
*
* @category Transforms
*/
export declare function prune(_options?: PruneOptions): Transform;

@@ -34,3 +34,5 @@ import { Transform } from '@gltf-transform/core';

* of the file.
*
* @category Transforms
*/
export declare function quantize(_options?: QuantizeOptions): Transform;

18

dist/reorder.d.ts

@@ -1,3 +0,2 @@

import { Accessor, Document, GLTF, Primitive, Transform } from '@gltf-transform/core';
import { SetMap } from './utils.js';
import { Transform } from '@gltf-transform/core';
/** Options for the {@link reorder} function. */

@@ -30,16 +29,5 @@ export interface ReorderOptions {

* ```
*
* @category Transforms
*/
export declare function reorder(_options: ReorderOptions): Transform;
interface LayoutPlan {
indicesToMode: Map<Accessor, GLTF.MeshPrimitiveMode>;
indicesToAttributes: SetMap<Accessor, Accessor>;
attributesToPrimitives: SetMap<Accessor, Primitive>;
}
/**
* Constructs a plan for processing vertex streams, based on unique
* index:attribute[] groups. Where different indices are used with the same
* attributes, we'll end up splitting the primitives to not share attributes,
* which appears to be consistent with the Meshopt implementation.
*/
export declare function createLayoutPlan(document: Document): LayoutPlan;
export {};
import { Transform } from '@gltf-transform/core';
import { resampleDebug } from 'keyframe-resample';
export interface ResampleOptions {
ready?: Promise<void>;
resample?: typeof resampleDebug;
tolerance?: number;

@@ -10,4 +13,18 @@ }

*
* Example: (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
* Result: (0,0,0,0,1,1,1,0,0,0,0,0,0,0) → (0,0,1,1,0,0)
*
* Example:
*
* ```
* import { ready, resample } from 'keyframe-resample';
*
* // JavaScript (slower)
* await document.transform(resample());
*
* // WebAssembly (faster)
* await document.transform(resample({ ready, resample }));
* ```
*
* @category Transforms
*/
export declare function resample(_options?: ResampleOptions): Transform;

@@ -14,3 +14,5 @@ import { Transform } from '@gltf-transform/core';

* Creates an {@link Animation} displaying each of the specified {@link Node}s sequentially.
*
* @category Transforms
*/
export declare function sequence(_options?: SequenceOptions): Transform;

@@ -48,4 +48,6 @@ import { Document, Primitive, Transform } from '@gltf-transform/core';

* - https://github.com/zeux/meshoptimizer/blob/master/js/README.md#simplifier
*
* @category Transforms
*/
export declare function simplify(_options: SimplifyOptions): Transform;
export declare function simplifyPrimitive(document: Document, prim: Primitive, _options: SimplifyOptions): Primitive;

@@ -0,0 +0,0 @@ import { Primitive, PrimitiveTarget } from '@gltf-transform/core';

@@ -30,3 +30,4 @@ import { Transform } from '@gltf-transform/core';

* @experimental
* @category Transforms
*/
export declare function sparse(_options?: SparseOptions): Transform;

@@ -29,3 +29,5 @@ import { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function tangents(_options?: TangentsOptions): Transform;

@@ -65,2 +65,4 @@ import { Texture, Transform, vec2 } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -67,0 +69,0 @@ export declare function textureCompress(_options: TextureCompressOptions): Transform;

@@ -26,8 +26,10 @@ import type { Transform, vec2 } from '@gltf-transform/core';

/**
* Resize PNG or JPEG {@link Texture Textures}, with {@link https://en.wikipedia.org/wiki/Lanczos_algorithm Lanczos filtering}.
* Resize PNG or JPEG {@link Texture Textures}, with [Lanczos filtering](https://en.wikipedia.org/wiki/Lanczos_algorithm).
*
* Implementation provided by {@link https://github.com/donmccurdy/ndarray-lanczos ndarray-lanczos}
* Implementation provided by [ndarray-lanczos](https://github.com/donmccurdy/ndarray-lanczos)
* package, which works in Web and Node.js environments. For a faster and more robust implementation
* based on Sharp (available only in Node.js), use {@link textureCompress} with the 'resize' option.
*
* @category Transforms
*/
export declare function textureResize(_options?: TextureResizeOptions): Transform;

@@ -0,0 +0,0 @@ import { mat4, Mesh } from '@gltf-transform/core';

@@ -0,0 +0,0 @@ import type { mat4, Primitive } from '@gltf-transform/core';

import type { Transform } from '@gltf-transform/core';
/**
* @category Transforms
*/
export declare function unlit(): Transform;

@@ -18,3 +18,5 @@ import type { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function unpartition(_options?: UnpartitionOptions): Transform;

@@ -11,3 +11,5 @@ import type { Transform } from '@gltf-transform/core';

* No options are currently implemented for this function.
*
* @category Transforms
*/
export declare function unweld(_options?: UnweldOptions): Transform;

@@ -0,0 +0,0 @@ import type { NdArray } from 'ndarray';

@@ -25,3 +25,5 @@ import type { Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/
export declare function vertexColorSpace(options: ColorSpaceOptions): Transform;

@@ -33,2 +33,4 @@ import { Document, Primitive, Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -35,0 +37,0 @@ export declare function weld(_options?: WeldOptions): Transform;

{
"name": "@gltf-transform/functions",
"version": "4.0.0-alpha.0",
"version": "4.0.0-alpha.1",
"repository": "github:donmccurdy/glTF-Transform",

@@ -26,4 +26,4 @@ "homepage": "https://gltf-transform.donmccurdy.com/functions.html",

"scripts": {
"dist": "microbundle --format modern,cjs --no-compress",
"watch": "microbundle watch --format modern,cjs --no-compress",
"dist": "microbundle --format modern,cjs",
"watch": "microbundle watch --format modern,cjs",
"watch:debug": "microbundle watch --format modern,cjs --no-compress"

@@ -39,4 +39,4 @@ },

"dependencies": {
"@gltf-transform/core": "^4.0.0-alpha.0",
"@gltf-transform/extensions": "^4.0.0-alpha.0",
"@gltf-transform/core": "^4.0.0-alpha.1",
"@gltf-transform/extensions": "^4.0.0-alpha.1",
"ktx-parse": "^0.5.0",

@@ -57,3 +57,3 @@ "ndarray": "^1.0.19",

},
"gitHead": "8d80d1ffa893ba3a337cffbf638cc84f75c44a5d"
"gitHead": "fb71aa67ca90f719b2d8d2d8fc759c49d5f9aa45"
}

@@ -10,75 +10,8 @@ # @gltf-transform/functions

- GitHub: https://github.com/donmccurdy/glTF-Transform
- Project Documentation: https://gltf-transform.donmccurdy.com/
- Package Documentation: https://gltf-transform.donmccurdy.com/functions.html
- Project Documentation: https://gltf-transform.donmccurdy.com
- Package Documentation: https://gltf-transform.donmccurdy.com/functions
## Quickstart
Install the scripting packages:
```bash
npm install --save @gltf-transform/core @gltf-transform/extensions @gltf-transform/functions
```
Read and write glTF scenes with platform I/O utilities [WebIO](https://gltf-transform.donmccurdy.com/classes/core.webio.html), [NodeIO](https://gltf-transform.donmccurdy.com/classes/core.nodeio.html), or [DenoIO](https://gltf-transform.donmccurdy.com/classes/core.denoio.html):
```typescript
import { Document, NodeIO } from '@gltf-transform/core';
import { KHRONOS_EXTENSIONS } from '@gltf-transform/extensions';
import draco3d from 'draco3dgltf';
// Configure I/O.
const io = new NodeIO()
.registerExtensions(KHRONOS_EXTENSIONS)
.registerDependencies({
'draco3d.decoder': await draco3d.createDecoderModule(), // Optional.
'draco3d.encoder': await draco3d.createEncoderModule(), // Optional.
});
// Read from URL.
const document = await io.read('path/to/model.glb');
// Write to byte array (Uint8Array).
const glb = await io.writeBinary(document);
```
To perform changes to an existing glTF [Document](https://gltf-transform.donmccurdy.com/classes/core.document.html), import off-the-shelf scripts from the [Functions](https://gltf-transform.donmccurdy.com/functions.html) package, or write your own using API classes like [Material](https://gltf-transform.donmccurdy.com/classes/core.material.html), [Primitive](https://gltf-transform.donmccurdy.com/classes/core.primitive.html), and [Texture](https://gltf-transform.donmccurdy.com/classes/core.texture.html).
```typescript
import { resample, prune, dedup, draco, textureCompress } from '@gltf-transform/functions';
import * as sharp from 'sharp'; // Node.js only.
await document.transform(
// Losslessly resample animation frames.
resample(),
// Remove unused nodes, textures, or other data.
prune(),
// Remove duplicate vertex or texture data, if any.
dedup(),
// Compress mesh geometry with Draco.
draco(),
// Convert textures to WebP (Requires glTF Transform v3 and Node.js).
textureCompress({
encoder: sharp,
targetFormat: 'webp',
resize: [1024, 2024],
}),
// Custom transform.
backfaceCulling({cull: true}),
);
// Custom transform: enable/disable backface culling.
function backfaceCulling(options) {
return (document) => {
for (const material of document.getRoot().listMaterials()) {
material.setDoubleSided(!options.cull);
}
};
}
```
To learn how glTF-Transform works, and the architecture of the scripting API, start with [Concepts](https://gltf-transform.donmccurdy.com/concepts.html). To try out the scripting API without installing anything, visit [gltf.report/](https://gltf.report/), load a glTF model, and open the *Script* tab.
## Credits
See [*Credits*](https://gltf-transform.donmccurdy.com/credits.html).
See [*Credits*](https://gltf-transform.donmccurdy.com/credits).

@@ -85,0 +18,0 @@ ## License

@@ -24,2 +24,4 @@ import type { Document, Transform, vec3 } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -26,0 +28,0 @@ export function center(_options: CenterOptions = CENTER_DEFAULTS): Transform {

@@ -49,2 +49,4 @@ import {

* ```
*
* @category Transforms
*/

@@ -77,7 +79,7 @@ export function dedup(_options: DedupOptions = DEDUP_DEFAULTS): Transform {

// Find all accessors used for mesh data.
const indicesAccessors: Set<Accessor> = new Set();
const attributeAccessors: Set<Accessor> = new Set();
const inputAccessors: Set<Accessor> = new Set();
const outputAccessors: Set<Accessor> = new Set();
// Find all accessors used for mesh and animation data.
const indicesMap = new Map<string, Set<Accessor>>();
const attributeMap = new Map<string, Set<Accessor>>();
const inputMap = new Map<string, Set<Accessor>>();
const outputMap = new Map<string, Set<Accessor>>();

@@ -87,5 +89,4 @@ const meshes = document.getRoot().listMeshes();

mesh.listPrimitives().forEach((primitive) => {
primitive.listAttributes().forEach((accessor) => attributeAccessors.add(accessor));
const indices = primitive.getIndices();
if (indices) indicesAccessors.add(indices);
primitive.listAttributes().forEach((accessor) => hashAccessor(accessor, attributeMap));
hashAccessor(primitive.getIndices(), indicesMap);
});

@@ -96,13 +97,27 @@ });

for (const sampler of animation.listSamplers()) {
const input = sampler.getInput();
const output = sampler.getOutput();
if (input) inputAccessors.add(input);
if (output) outputAccessors.add(output);
hashAccessor(sampler.getInput(), inputMap);
hashAccessor(sampler.getOutput(), outputMap);
}
}
// Add accessor to the appropriate hash group. Hashes are _non-unique_,
// intended to quickly compare everything accept the underlying array.
function hashAccessor(accessor: Accessor | null, group: Map<string, Set<Accessor>>): void {
if (!accessor) return;
const hash = [
accessor.getCount(),
accessor.getType(),
accessor.getComponentType(),
accessor.getNormalized(),
accessor.getSparse(),
].join(':');
let hashSet = group.get(hash);
if (!hashSet) group.set(hash, (hashSet = new Set<Accessor>()));
hashSet.add(accessor);
}
// Find duplicate accessors of a given type.
function detectDuplicates(accessors: Accessor[]): Map<Accessor, Accessor> {
const duplicateAccessors: Map<Accessor, Accessor> = new Map();
function detectDuplicates(accessors: Accessor[], duplicates: Map<Accessor, Accessor>): void {
for (let i = 0; i < accessors.length; i++) {

@@ -112,3 +127,3 @@ const a = accessors[i];

if (duplicateAccessors.has(a)) continue;
if (duplicates.has(a)) continue;

@@ -118,32 +133,25 @@ for (let j = i + 1; j < accessors.length; j++) {

if (duplicateAccessors.has(b)) continue;
if (duplicates.has(b)) continue;
if (a.getType() !== b.getType()) continue;
if (a.getComponentType() !== b.getComponentType()) continue;
if (a.getCount() !== b.getCount()) continue;
if (a.getNormalized() !== b.getNormalized()) continue;
// Just compare the arrays — everything else was covered by the
// hash. Comparing uint8 views is faster than comparing the
// original typed arrays.
if (BufferUtils.equals(aData, BufferUtils.toView(b.getArray()!))) {
duplicateAccessors.set(b, a);
duplicates.set(b, a);
}
}
}
}
return duplicateAccessors;
let total = 0;
const duplicates = new Map<Accessor, Accessor>();
for (const group of [attributeMap, indicesMap, inputMap, outputMap]) {
for (const hashGroup of group.values()) {
total += hashGroup.size;
detectDuplicates(Array.from(hashGroup), duplicates);
}
}
const duplicateIndices = detectDuplicates(Array.from(indicesAccessors));
logger.debug(`${NAME}: Found ${duplicateIndices.size} duplicates among ${indicesAccessors.size} indices.`);
logger.debug(`${NAME}: Found ${duplicates.size} duplicates among ${total} accessors.`);
const duplicateAttributes = detectDuplicates(Array.from(attributeAccessors));
logger.debug(
`${NAME}: Found ${duplicateAttributes.size} duplicates among ${attributeAccessors.size}` + ' attributes.'
);
const duplicateInputs = detectDuplicates(Array.from(inputAccessors));
const duplicateOutputs = detectDuplicates(Array.from(outputAccessors));
logger.debug(
`${NAME}: Found ${duplicateInputs.size + duplicateOutputs.size} duplicates among` +
` ${inputAccessors.size + outputAccessors.size} animation accessors.`
);
// Dissolve duplicate vertex attributes and indices.

@@ -153,14 +161,12 @@ meshes.forEach((mesh) => {

primitive.listAttributes().forEach((accessor) => {
if (duplicateAttributes.has(accessor)) {
primitive.swap(accessor, duplicateAttributes.get(accessor) as Accessor);
if (duplicates.has(accessor)) {
primitive.swap(accessor, duplicates.get(accessor) as Accessor);
}
});
const indices = primitive.getIndices();
if (indices && duplicateIndices.has(indices)) {
primitive.swap(indices, duplicateIndices.get(indices) as Accessor);
if (indices && duplicates.has(indices)) {
primitive.swap(indices, duplicates.get(indices) as Accessor);
}
});
});
Array.from(duplicateIndices.keys()).forEach((indices) => indices.dispose());
Array.from(duplicateAttributes.keys()).forEach((attribute) => attribute.dispose());

@@ -172,12 +178,14 @@ // Dissolve duplicate animation sampler inputs and outputs.

const output = sampler.getOutput();
if (input && duplicateInputs.has(input)) {
sampler.swap(input, duplicateInputs.get(input) as Accessor);
if (input && duplicates.has(input)) {
sampler.swap(input, duplicates.get(input) as Accessor);
}
if (output && duplicateOutputs.has(output)) {
sampler.swap(output, duplicateOutputs.get(output) as Accessor);
if (output && duplicates.has(output)) {
sampler.swap(output, duplicates.get(output) as Accessor);
}
}
}
Array.from(duplicateInputs.keys()).forEach((input) => input.dispose());
Array.from(duplicateOutputs.keys()).forEach((output) => output.dispose());
Array.from(duplicates.keys()).forEach((accessor) => accessor.dispose());
logger.debug(`${NAME}: Complete.`);
}

@@ -184,0 +192,0 @@

@@ -24,2 +24,4 @@ import type { Accessor, Document, Primitive, Transform } from '@gltf-transform/core';

* necessary for compatibility with applications that don't support quantization.
*
* @category Transforms
*/

@@ -26,0 +28,0 @@ export function dequantize(_options: DequantizeOptions = DEQUANTIZE_DEFAULTS): Transform {

@@ -37,2 +37,4 @@ import type { Document, Transform } from '@gltf-transform/core';

* This function is a thin wrapper around the {@link KHRDracoMeshCompression} extension itself.
*
* @category Transforms
*/

@@ -39,0 +41,0 @@ export function draco(_options: DracoOptions = DRACO_DEFAULTS): Transform {

@@ -30,2 +30,4 @@ import { Document, Node, PropertyType, Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -32,0 +34,0 @@ export function flatten(_options: FlattenOptions = FLATTEN_DEFAULTS): Transform {

@@ -61,2 +61,3 @@ /**

export * from './get-node-scene.js';
export * from './get-texture-color-space.js';
export * from './inspect.js';

@@ -73,2 +74,3 @@ export * from './instance.js';

export * from './normals.js';
export * from './palette.js';
export * from './partition.js';

@@ -75,0 +77,0 @@ export * from './prune.js';

@@ -32,2 +32,4 @@ import { Document, ILogger, MathUtils, Mesh, Node, Transform, vec3, vec4 } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -40,3 +42,2 @@ export function instance(_options: InstanceOptions = INSTANCE_DEFAULTS): Transform {

const root = doc.getRoot();
const batchExtension = doc.createExtension(EXTMeshGPUInstancing);

@@ -49,2 +50,4 @@ if (root.listAnimations().length) {

const batchExtension = doc.createExtension(EXTMeshGPUInstancing);
let numBatches = 0;

@@ -51,0 +54,0 @@ let numInstances = 0;

@@ -77,2 +77,4 @@ import {

* ```
*
* @category Transforms
*/

@@ -79,0 +81,0 @@ export function join(_options: JoinOptions = JOIN_DEFAULTS): Transform {

@@ -17,2 +17,3 @@ import { Document, Texture } from '@gltf-transform/core';

* }
* ```
*/

@@ -19,0 +20,0 @@ export function listTextureChannels(texture: Texture): TextureChannel[] {

@@ -1,9 +0,12 @@

import { Texture, TextureInfo } from '@gltf-transform/core';
import { ExtensionProperty, Material, Property, Texture, TextureInfo } from '@gltf-transform/core';
/**
* Lists all {@link TextureInfo} definitions associated with a given {@link Texture}.
* Lists all {@link TextureInfo} definitions associated with a given
* {@link Texture}. May be used to determine which UV transforms
* and texCoord indices are applied to the material, without explicitly
* checking the material properties and extensions.
*
* Example:
*
* ```js
* ```typescript
* // Find TextureInfo instances associated with the texture.

@@ -14,3 +17,3 @@ * const results = listTextureInfo(texture);

* const texCoords = results.map((info) => info.getTexCoord());
* // → [0, 0, 1]
* // → [0, 1]
* ```

@@ -20,3 +23,3 @@ */

const graph = texture.getGraph();
const results: TextureInfo[] = [];
const results = new Set<TextureInfo>();

@@ -30,3 +33,3 @@ for (const textureEdge of graph.listParentEdges(texture)) {

if (child instanceof TextureInfo && edge.getName() === name) {
results.push(child);
results.add(child);
}

@@ -36,3 +39,42 @@ }

return results;
return Array.from(results);
}
/**
* Lists all {@link TextureInfo} definitions associated with any {@link Texture}
* on the given {@link Material}. May be used to determine which UV transforms
* and texCoord indices are applied to the material, without explicitly
* checking the material properties and extensions.
*
* Example:
*
* ```typescript
* const results = listTextureInfoByMaterial(material);
*
* const texCoords = results.map((info) => info.getTexCoord());
* // → [0, 1]
* ```
*/
export function listTextureInfoByMaterial(material: Material): TextureInfo[] {
const graph = material.getGraph();
const visited = new Set<Property>();
const results = new Set<TextureInfo>();
function traverse(prop: Material | ExtensionProperty) {
for (const child of graph.listChildren(prop)) {
if (visited.has(child)) continue;
visited.add(child);
if (child instanceof Texture) {
for (const textureInfo of listTextureInfo(child)) {
results.add(textureInfo);
}
} else if (child instanceof ExtensionProperty) {
traverse(child);
}
}
}
traverse(material);
return Array.from(results);
}

@@ -39,2 +39,4 @@ import type { Document, Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -41,0 +43,0 @@ export function meshopt(_options: MeshoptOptions): Transform {

@@ -24,2 +24,4 @@ import type { Document, Texture, Transform } from '@gltf-transform/core';

* No options are currently implemented for this function.
*
* @category Transforms
*/

@@ -26,0 +28,0 @@ export function metalRough(_options: MetalRoughOptions = METALROUGH_DEFAULTS): Transform {

@@ -28,2 +28,4 @@ import type { Document, Transform, vec3 } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -30,0 +32,0 @@ export function normals(_options: NormalsOptions = NORMALS_DEFAULTS): Transform {

@@ -31,2 +31,4 @@ import { Document, ILogger, PropertyType, Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -33,0 +35,0 @@ export function partition(_options: PartitionOptions = PARTITION_DEFAULTS): Transform {

@@ -19,2 +19,3 @@ import {

import { createTransform } from './utils.js';
import { listTextureInfoByMaterial } from './list-texture-info.js';

@@ -65,2 +66,4 @@ const NAME = 'prune';

* No options are currently implemented for this function.
*
* @category Transforms
*/

@@ -106,10 +109,20 @@ export function prune(_options: PruneOptions = PRUNE_DEFAULTS): Transform {

if (!options.keepAttributes && propertyTypes.has(PropertyType.ACCESSOR)) {
const materialPrims = new Map<Material, Set<Primitive>>();
for (const mesh of root.listMeshes()) {
for (const prim of mesh.listPrimitives()) {
const required = listRequiredSemantics(doc, prim.getMaterial());
const material = prim.getMaterial();
const required = listRequiredSemantics(doc, material);
const unused = listUnusedSemantics(prim, required);
pruneAttributes(prim, unused);
prim.listTargets().forEach((target) => pruneAttributes(target, unused));
if (material) {
materialPrims.has(material)
? materialPrims.get(material)!.add(prim)
: materialPrims.set(material, new Set([prim]));
}
}
}
for (const [material, prims] of materialPrims) {
shiftTexCoords(material, Array.from(prims));
}
}

@@ -279,1 +292,49 @@

}
/**
* Shifts texCoord indices on the given material and primitives assigned to
* that material, such that indices start at zero and ascend without gaps.
* Prior to calling this function, the implementation must ensure that:
* - All TEXCOORD_n attributes on these prims are used by the material.
* - Material does not require any unavailable TEXCOORD_n attributes.
*
* TEXCOORD_n attributes on morph targets are shifted alongside the parent
* prim, but gaps may remain in their semantic lists.
*/
function shiftTexCoords(material: Material, prims: Primitive[]) {
// Create map from srcTexCoord → dstTexCoord.
const textureInfoList = listTextureInfoByMaterial(material);
const texCoordSet = new Set(textureInfoList.map((info: TextureInfo) => info.getTexCoord()));
const texCoordList = Array.from(texCoordSet).sort();
const texCoordMap = new Map(texCoordList.map((texCoord, index) => [texCoord, index]));
const semanticMap = new Map(texCoordList.map((texCoord, index) => [`TEXCOORD_${texCoord}`, `TEXCOORD_${index}`]));
// Update material.
for (const textureInfo of textureInfoList) {
const texCoord = textureInfo.getTexCoord();
textureInfo.setTexCoord(texCoordMap.get(texCoord)!);
}
// Update prims.
for (const prim of prims) {
const semantics = prim
.listSemantics()
.filter((semantic) => semantic.startsWith('TEXCOORD_'))
.sort();
updatePrim(prim, semantics);
prim.listTargets().forEach((target) => updatePrim(target, semantics));
}
function updatePrim(prim: Primitive | PrimitiveTarget, srcSemantics: string[]) {
for (const srcSemantic of srcSemantics) {
const uv = prim.getAttribute(srcSemantic);
if (!uv) continue;
const dstSemantic = semanticMap.get(srcSemantic)!;
if (dstSemantic === srcSemantic) continue;
prim.setAttribute(dstSemantic, uv);
prim.setAttribute(srcSemantic, null);
}
}
}

@@ -86,2 +86,4 @@ import {

* of the file.
*
* @category Transforms
*/

@@ -88,0 +90,0 @@ export function quantize(_options: QuantizeOptions = QUANTIZE_DEFAULTS): Transform {

@@ -40,2 +40,4 @@ import { Accessor, Document, GLTF, Primitive, PropertyType, Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -102,2 +104,3 @@ export function reorder(_options: ReorderOptions): Transform {

/** @hidden */
interface LayoutPlan {

@@ -114,4 +117,6 @@ indicesToMode: Map<Accessor, GLTF.MeshPrimitiveMode>;

* which appears to be consistent with the Meshopt implementation.
*
* @hidden
*/
export function createLayoutPlan(document: Document): LayoutPlan {
function createLayoutPlan(document: Document): LayoutPlan {
const indicesToAttributes = new SetMap<Accessor, Accessor>();

@@ -118,0 +123,0 @@ const indicesToMode = new Map<Accessor, GLTF.MeshPrimitiveMode>();

import {
Accessor,
AnimationSampler,
ComponentTypeToTypedArray,
Document,

@@ -11,14 +12,23 @@ GLTF,

TransformContext,
TypedArray,
} from '@gltf-transform/core';
import quat, { getAngle, slerp } from 'gl-matrix/quat';
import { dedup } from './dedup.js';
import { createTransform, isTransformPending } from './utils.js';
import { resampleDebug } from 'keyframe-resample';
const NAME = 'resample';
const EMPTY_ARRAY = new Float32Array(0);
export interface ResampleOptions {
ready?: Promise<void>;
resample?: typeof resampleDebug;
tolerance?: number;
}
const RESAMPLE_DEFAULTS: Required<ResampleOptions> = { tolerance: 1e-4 };
const RESAMPLE_DEFAULTS: Required<ResampleOptions> = {
ready: Promise.resolve(),
resample: resampleDebug,
tolerance: 1e-4,
};

@@ -30,3 +40,17 @@ /**

*
* Example: (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
* Result: (0,0,0,0,1,1,1,0,0,0,0,0,0,0) → (0,0,1,1,0,0)
*
* Example:
*
* ```
* import { ready, resample } from 'keyframe-resample';
*
* // JavaScript (slower)
* await document.transform(resample());
*
* // WebAssembly (faster)
* await document.transform(resample({ ready, resample }));
* ```
*
* @category Transforms
*/

@@ -41,6 +65,8 @@ export function resample(_options: ResampleOptions = RESAMPLE_DEFAULTS): Transform {

let didSkipMorphTargets = false;
const ready = options.ready;
const resample = options.resample;
await ready;
for (const animation of document.getRoot().listAnimations()) {
// Skip morph targets, see https://github.com/donmccurdy/glTF-Transform/issues/290.
const samplerTargetPaths = new Map<AnimationSampler, GLTF.AnimationChannelTargetPath>();

@@ -52,11 +78,62 @@ for (const channel of animation.listChannels()) {

for (const sampler of animation.listSamplers()) {
if (samplerTargetPaths.get(sampler) === 'weights') {
didSkipMorphTargets = true;
continue;
const samplerInterpolation = sampler.getInterpolation();
if (samplerInterpolation === 'STEP' || samplerInterpolation === 'LINEAR') {
const input = sampler.getInput()!;
const output = sampler.getOutput()!;
accessorsVisited.add(input);
accessorsVisited.add(output);
// prettier-ignore
const tmpTimes = toFloat32Array(
input.getArray()!,
input.getComponentType(),
input.getNormalized()
);
const tmpValues = toFloat32Array(
output.getArray()!,
output.getComponentType(),
output.getNormalized()
);
const elementSize = tmpValues.length / tmpTimes.length;
const srcCount = tmpTimes.length;
let dstCount: number;
if (samplerInterpolation === 'STEP') {
dstCount = resample(tmpTimes, tmpValues, 'step', options.tolerance);
} else if (samplerTargetPaths.get(sampler) === 'rotation') {
dstCount = resample(tmpTimes, tmpValues, 'slerp', options.tolerance);
} else {
dstCount = resample(tmpTimes, tmpValues, 'lerp', options.tolerance);
}
if (dstCount < srcCount) {
// Clone the input/output accessors, without cloning their underlying
// arrays. Then assign the resampled data.
const srcTimes = input.getArray()!;
const srcValues = output.getArray()!;
const dstTimes = fromFloat32Array(
new Float32Array(tmpTimes.buffer, tmpTimes.byteOffset, dstCount),
input.getComponentType(),
input.getNormalized()
);
const dstValues = fromFloat32Array(
new Float32Array(tmpValues.buffer, tmpValues.byteOffset, dstCount * elementSize),
output.getComponentType(),
output.getNormalized()
);
input.setArray(EMPTY_ARRAY);
output.setArray(EMPTY_ARRAY);
sampler.setInput(input.clone().setArray(dstTimes));
sampler.setOutput(output.clone().setArray(dstValues));
input.setArray(srcTimes);
output.setArray(srcValues);
}
}
if (sampler.getInterpolation() === 'STEP' || sampler.getInterpolation() === 'LINEAR') {
accessorsVisited.add(sampler.getInput()!);
accessorsVisited.add(sampler.getOutput()!);
optimize(sampler, samplerTargetPaths.get(sampler)!, options);
}
}

@@ -77,6 +154,2 @@ }

if (didSkipMorphTargets) {
logger.warn(`${NAME}: Skipped optimizing morph target keyframes, not yet supported.`);
}
logger.debug(`${NAME}: Complete.`);

@@ -86,82 +159,34 @@ });

function optimize(sampler: AnimationSampler, path: GLTF.AnimationChannelTargetPath, options: ResampleOptions): void {
const input = sampler.getInput()!.clone().setSparse(false);
const output = sampler.getOutput()!.clone().setSparse(false);
/** Returns a copy of the source array, as a denormalized Float32Array. */
function toFloat32Array(
srcArray: TypedArray,
componentType: GLTF.AccessorComponentType,
normalized: boolean
): Float32Array {
if (srcArray instanceof Float32Array) return srcArray.slice();
const dstArray = new Float32Array(srcArray);
if (!normalized) return dstArray;
const tolerance = options.tolerance as number;
const interpolation = sampler.getInterpolation();
for (let i = 0; i < dstArray.length; i++) {
dstArray[i] = MathUtils.decodeNormalizedInt(dstArray[i], componentType);
}
const lastIndex = input.getCount() - 1;
const tmp: number[] = [];
const value: number[] = [];
const valueNext: number[] = [];
const valuePrev: number[] = [];
return dstArray;
}
let writeIndex = 1;
/** Returns a copy of the source array, with specified component type and normalization. */
function fromFloat32Array(
srcArray: Float32Array,
componentType: GLTF.AccessorComponentType,
normalized: boolean
): TypedArray {
if (componentType === Accessor.ComponentType.FLOAT) return srcArray.slice();
const TypedArray = ComponentTypeToTypedArray[componentType];
const dstArray = new TypedArray(srcArray.length);
for (let i = 1; i < lastIndex; ++i) {
const timePrev = input.getScalar(writeIndex - 1);
const time = input.getScalar(i);
const timeNext = input.getScalar(i + 1);
const t = (time - timePrev) / (timeNext - timePrev);
let keep = false;
// Remove unnecessary adjacent keyframes.
if (time !== timeNext && (i !== 1 || time !== input.getScalar(0))) {
output.getElement(writeIndex - 1, valuePrev);
output.getElement(i, value);
output.getElement(i + 1, valueNext);
if (interpolation === 'LINEAR' && path === 'rotation') {
// Prune keyframes colinear with prev/next keyframes.
const sample = slerp(tmp as quat, valuePrev as quat, valueNext as quat, t) as number[];
const angle = getAngle(valuePrev as quat, value as quat) + getAngle(value as quat, valueNext as quat);
keep = !MathUtils.eq(value, sample, tolerance) || angle + Number.EPSILON >= Math.PI;
} else if (interpolation === 'LINEAR') {
// Prune keyframes colinear with prev/next keyframes.
const sample = vlerp(tmp, valuePrev, valueNext, t);
keep = !MathUtils.eq(value, sample, tolerance);
} else if (interpolation === 'STEP') {
// Prune keyframes identical to prev/next keyframes.
keep = !MathUtils.eq(value, valuePrev) || !MathUtils.eq(value, valueNext);
}
}
// In-place compaction.
if (keep) {
if (i !== writeIndex) {
input.setScalar(writeIndex, input.getScalar(i));
output.setElement(writeIndex, output.getElement(i, tmp));
}
writeIndex++;
}
for (let i = 0; i < dstArray.length; i++) {
dstArray[i] = normalized ? MathUtils.encodeNormalizedInt(srcArray[i], componentType) : srcArray[i];
}
// Flush last keyframe (compaction looks ahead).
if (lastIndex > 0) {
input.setScalar(writeIndex, input.getScalar(lastIndex));
output.setElement(writeIndex, output.getElement(lastIndex, tmp));
writeIndex++;
}
// If the sampler was optimized, truncate and save the results. If not, clean up.
if (writeIndex !== input.getCount()) {
input.setArray(input.getArray()!.slice(0, writeIndex));
output.setArray(output.getArray()!.slice(0, writeIndex * output.getElementSize()));
sampler.setInput(input);
sampler.setOutput(output);
} else {
input.dispose();
output.dispose();
}
return dstArray;
}
function lerp(v0: number, v1: number, t: number): number {
return v0 * (1 - t) + v1 * t;
}
function vlerp(out: number[], a: number[], b: number[], t: number): number[] {
for (let i = 0; i < a.length; i++) out[i] = lerp(a[i], b[i], t);
return out;
}

@@ -26,8 +26,9 @@ import { Accessor, AnimationChannel, AnimationSampler, Document, Transform } from '@gltf-transform/core';

* Creates an {@link Animation} displaying each of the specified {@link Node}s sequentially.
*
* @category Transforms
*/
export function sequence (_options: SequenceOptions = SEQUENCE_DEFAULTS): Transform {
const options = {...SEQUENCE_DEFAULTS, ..._options} as Required<SequenceOptions>;
export function sequence(_options: SequenceOptions = SEQUENCE_DEFAULTS): Transform {
const options = { ...SEQUENCE_DEFAULTS, ..._options } as Required<SequenceOptions>;
return createTransform(NAME, (doc: Document): void => {
const logger = doc.getLogger();

@@ -38,8 +39,7 @@ const root = doc.getRoot();

// Collect sequence nodes.
const sequenceNodes = root.listNodes()
.filter((node) => node.getName().match(options.pattern));
const sequenceNodes = root.listNodes().filter((node) => node.getName().match(options.pattern));
// Sort by node name.
if (options.sort) {
sequenceNodes.sort((a, b) => a.getName() > b.getName() ? 1 : -1);
sequenceNodes.sort((a, b) => (a.getName() > b.getName() ? 1 : -1));
}

@@ -66,14 +66,15 @@

// Append channel to animation sequence.
const input = doc.createAccessor()
.setArray(new Float32Array(inputArray))
.setBuffer(animBuffer);
const output = doc.createAccessor()
const input = doc.createAccessor().setArray(new Float32Array(inputArray)).setBuffer(animBuffer);
const output = doc
.createAccessor()
.setArray(new Float32Array(outputArray))
.setBuffer(animBuffer)
.setType(Accessor.Type.VEC3);
const sampler = doc.createAnimationSampler()
const sampler = doc
.createAnimationSampler()
.setInterpolation(AnimationSampler.Interpolation.STEP)
.setInput(input)
.setOutput(output);
const channel = doc.createAnimationChannel()
const channel = doc
.createAnimationChannel()
.setTargetNode(node)

@@ -86,5 +87,3 @@ .setTargetPath(AnimationChannel.TargetPath.SCALE)

logger.debug(`${NAME}: Complete.`);
});
}

@@ -68,2 +68,4 @@ import { Accessor, Document, Primitive, PropertyType, Transform, TransformContext } from '@gltf-transform/core';

* - https://github.com/zeux/meshoptimizer/blob/master/js/README.md#simplifier
*
* @category Transforms
*/

@@ -70,0 +72,0 @@ export function simplify(_options: SimplifyOptions): Transform {

@@ -39,2 +39,3 @@ import { Document, MathUtils, Transform } from '@gltf-transform/core';

* @experimental
* @category Transforms
*/

@@ -41,0 +42,0 @@ export function sparse(_options: SparseOptions = SPARSE_DEFAULTS): Transform {

@@ -38,2 +38,4 @@ import { Accessor, Document, ILogger, Primitive, Transform, TypedArray, uuid } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -40,0 +42,0 @@ export function tangents(_options: TangentsOptions = TANGENTS_DEFAULTS): Transform {

@@ -89,2 +89,4 @@ import { BufferUtils, Document, ImageUtils, Texture, TextureChannel, Transform, vec2 } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -91,0 +93,0 @@ export function textureCompress(_options: TextureCompressOptions): Transform {

@@ -42,7 +42,9 @@ import ndarray from 'ndarray';

/**
* Resize PNG or JPEG {@link Texture Textures}, with {@link https://en.wikipedia.org/wiki/Lanczos_algorithm Lanczos filtering}.
* Resize PNG or JPEG {@link Texture Textures}, with [Lanczos filtering](https://en.wikipedia.org/wiki/Lanczos_algorithm).
*
* Implementation provided by {@link https://github.com/donmccurdy/ndarray-lanczos ndarray-lanczos}
* Implementation provided by [ndarray-lanczos](https://github.com/donmccurdy/ndarray-lanczos)
* package, which works in Web and Node.js environments. For a faster and more robust implementation
* based on Sharp (available only in Node.js), use {@link textureCompress} with the 'resize' option.
*
* @category Transforms
*/

@@ -49,0 +51,0 @@ export function textureResize(_options: TextureResizeOptions = TEXTURE_RESIZE_DEFAULTS): Transform {

import type { Document, Transform } from '@gltf-transform/core';
import { KHRMaterialsUnlit } from '@gltf-transform/extensions';
/**
* @category Transforms
*/
export function unlit(): Transform {

@@ -5,0 +8,0 @@ return (doc: Document): void => {

@@ -24,2 +24,4 @@ import type { Document, Transform } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -26,0 +28,0 @@ export function unpartition(_options: UnpartitionOptions = UNPARTITION_DEFAULTS): Transform {

@@ -1,2 +0,1 @@

import type { Accessor, Document, ILogger, Transform, TypedArray } from '@gltf-transform/core';

@@ -19,2 +18,4 @@ import { createTransform, formatDeltaOp } from './utils.js';

* No options are currently implemented for this function.
*
* @category Transforms
*/

@@ -21,0 +22,0 @@ export function unweld(_options: UnweldOptions = UNWELD_DEFAULTS): Transform {

@@ -31,2 +31,4 @@ import type { Accessor, Document, Primitive, Transform, vec3 } from '@gltf-transform/core';

* ```
*
* @category Transforms
*/

@@ -33,0 +35,0 @@ export function vertexColorSpace(options: ColorSpaceOptions): Transform {

@@ -93,2 +93,4 @@ import {

* ```
*
* @category Transforms
*/

@@ -95,0 +97,0 @@ export function weld(_options: WeldOptions = WELD_DEFAULTS): Transform {

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