glTF-Transform icon indicating copy to clipboard operation
glTF-Transform copied to clipboard

dedup: animation samplers

Open kzhsw opened this issue 3 months ago • 1 comments

Is your feature request related to a problem? Please describe. Currently dedup is only available for accessors, meshes, images, materials, and skins, but in gltf spec, samplers are defined to be able to be reused, this should help in case of models with duplicates in samplers.

Describe the solution you'd like Add a PropertyType.ANIMATION_SAMPLER to options.propertyTypes of dedup, that enables animation sampler deduplication inside every gltf animation.

Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.

Additional context Add any other context or screenshots about the feature request here.

Attach some code here


function hashAnimationSampler(sampler: AnimationSampler): string {
	const hashKeys: (string | number | boolean)[] = [sampler.getInterpolation()];
	const input = sampler.getInput();
	if (input) {
		hashKeys.push(
			input.getCount(),
			input.getType(),
			input.getComponentType(),
			input.getNormalized(),
			input.getSparse(),
		);
	}
	const output = sampler.getOutput();
	if (output) {
		hashKeys.push(
			output.getCount(),
			output.getType(),
			output.getComponentType(),
			output.getNormalized(),
			output.getSparse(),
		);
	}
	return hashKeys.join(':');
}

function dedupAnimationSamplers(document: Document): void {
	const logger = document.getLogger();
	const root = document.getRoot();
	const animations = root.listAnimations();
	const samplerMap = new Map<string, AnimationSampler[]>();
	const duplicates = new Map<AnimationSampler, AnimationSampler>();
	let duplicateCount = 0, totalCount = 0;
	for (let i = 0, length = animations.length; i < length; i++) {
		const animation = animations[i];
		const samplers = animation.listSamplers();
		totalCount += samplers.length;
		for (let j = 0, samplersLength = samplers.length; j < samplersLength; j++) {
			const sampler = samplers[j];
			if (duplicates.has(sampler)) continue;
			const hashKey = hashAnimationSampler(sampler);
			const values = samplerMap.get(hashKey);
			if (!values) {
				samplerMap.set(hashKey, [sampler]);
				continue;
			}
			let isDuplicate = false;
			for (let k = 0, valueCount = values.length; k < valueCount; k++) {
				const value = values[k];
				// Instead of equals(), only do shallow compare here
				// For a deep compare, dedup accessors before this
				if (sampler.getInterpolation() === value.getInterpolation() &&
					sampler.getInput() === value.getInput() &&
					sampler.getOutput() === value.getOutput()) {
					duplicates.set(sampler, value);
					isDuplicate = true;
					break;
				}
			}
			if (!isDuplicate) {
				values.push(sampler);
			}

		}
		duplicateCount += duplicates.size;
		duplicates.forEach((dst, src) => {
			src.listParents().forEach((property) => {
				if (!(property instanceof Root)) property.swap(src, dst);
			});
			src.dispose();
		});
		samplerMap.clear();
		duplicates.clear();
	}

	logger.debug(`${NAME}: Merged ${duplicateCount} of ${totalCount} animation samplers.`);

}

kzhsw avatar Sep 27 '25 07:09 kzhsw

Another thing to discuss is in dedupAnimationSamplers, input and output are compared by reference, but hashAnimationSampler uses value hash, would it be better to have something like a Map-based reference counter, or is there some unique object id elsewhere

kzhsw avatar Oct 10 '25 02:10 kzhsw