@gltf-transform/functions
Version:
Functions for common glTF modifications, written using the core API
78 lines (66 loc) • 2.21 kB
text/typescript
import { Document, MathUtils, Transform } from '@gltf-transform/core';
import { assignDefaults, createTransform } from './utils.js';
const NAME = 'sparse';
/** Options for the {@link sparse} function. */
export interface SparseOptions {
/**
* Threshold ratio used to determine when an accessor should be sparse.
* Default: 1 / 3.
*/
ratio: number;
}
const SPARSE_DEFAULTS: Required<SparseOptions> = {
ratio: 1 / 3,
};
/**
* Scans all {@link Accessor Accessors} in the Document, detecting whether each Accessor
* would benefit from sparse data storage. Currently, sparse data storage is used only
* when many values (>= ratio) are zeroes. Particularly for assets using morph target
* ("shape key") animation, sparse data storage may significantly reduce file sizes.
*
* Example:
*
* ```ts
* import { sparse } from '@gltf-transform/functions';
*
* accessor.getArray(); // → [ 0, 0, 0, 0, 0, 25.0, 0, 0, ... ]
* accessor.getSparse(); // → false
*
* await document.transform(sparse({ratio: 1 / 10}));
*
* accessor.getSparse(); // → true
* ```
*
* @experimental
* @category Transforms
*/
export function sparse(_options: SparseOptions = SPARSE_DEFAULTS): Transform {
const options = assignDefaults(SPARSE_DEFAULTS, _options);
const ratio = options.ratio;
if (ratio < 0 || ratio > 1) {
throw new Error(`${NAME}: Ratio must be between 0 and 1.`);
}
return createTransform(NAME, (document: Document): void => {
const root = document.getRoot();
const logger = document.getLogger();
let modifiedCount = 0;
for (const accessor of root.listAccessors()) {
const count = accessor.getCount();
const base = Array(accessor.getElementSize()).fill(0);
const el = Array(accessor.getElementSize()).fill(0);
let nonZeroCount = 0;
for (let i = 0; i < count; i++) {
accessor.getElement(i, el);
if (!MathUtils.eq(el, base, 0)) nonZeroCount++;
if (nonZeroCount / count >= ratio) break;
}
const sparse = nonZeroCount / count < ratio;
if (sparse !== accessor.getSparse()) {
accessor.setSparse(sparse);
modifiedCount++;
}
}
logger.debug(`${NAME}: Updated ${modifiedCount} accessors.`);
logger.debug(`${NAME}: Complete.`);
});
}