kzhsw
October 11, 2025, 3:38am
1
In glTFLoaderAnimation, animation is constructed with setKeys()
/** @internal */
public constructor(
public readonly type: number,
public readonly name: string,
public readonly getValue: GetValueFn,
public readonly getStride: (target: any) => number
) {}
protected _buildAnimation(name: string, fps: number, keys: any[]): Animation {
const babylonAnimation = new Animation(name, this.name, fps, this.type);
babylonAnimation.setKeys(keys);
return babylonAnimation;
}
/** @internal */
public abstract buildAnimations(target: any, name: string, fps: number, keys: any[]): { babylonAnimatable: IAnimatable; babylonAnimation: Animation }[];
}
/** @internal */
export class TransformNodeAnimationPropertyInfo extends AnimationPropertyInfo {
/** @internal */
And setKeys defaults to clone keys
return clone;
}
/**
* Sets the key frames of the animation
* @param values The animation key frames to set
* @param dontClone Whether to clone the keys or not (default is false, so the array of keys is cloned)
*/
public setKeys(values: Array<IAnimationKey>, dontClone = false): void {
this._keys = !dontClone ? values.slice(0) : values;
}
/**
* Creates a key for the frame passed as a parameter and adds it to the animation IF a key doesn't already exist for that frame
* @param frame Frame number
* @returns The key index if the key was added or the index of the pre existing key if the frame passed as parameter already has a corresponding key
*/
public createKeyForFrame(frame: number) {
// Find the key corresponding to frame
EvaluateAnimationState.key = 0;
Since these keys are built in-place for each channel (should be for each sampler, since gltf samplers can be reused, and we can discuss it later), keys should not be cloned, to reduce gc pressure
3 Likes
cc @bghgary and @alexchuber but I guess a PR would be welcomed
Seems reasonable. I don’t know how to test this though. How do we ensure this doesn’t break anything?
kzhsw
October 25, 2025, 1:53am
4
Well, by static analysis. keys are created in callback in _loadAnimationChannelFromTargetInfoAsync, and passed to propertyInfo.buildAnimations, and never escaped from the scope.
const propertyInfos = targetInfo.info;
// Extract the corresponding values from the read value.
// GLTF values may be dispatched to several Babylon properties.
// For example, baseColorFactor [`r`, `g`, `b`, `a`] is dispatched to
// - albedoColor as Color3(`r`, `g`, `b`)
// - alpha as `a`
for (const propertyInfo of propertyInfos) {
const stride = propertyInfo.getStride(target);
const input = data.input;
const output = data.output;
const keys = new Array<IAnimationKey>(input.length);
let outputOffset = 0;
switch (data.interpolation) {
case AnimationSamplerInterpolation.STEP: {
for (let index = 0; index < input.length; index++) {
const value = propertyInfo.getValue(target, output, outputOffset, 1);
outputOffset += stride;
keys[index] = {
frame: input[index] * fps,
Ahh, sorry, I misunderstand what was happening, read the code too fast Yes, this seems good. Did you want to create a PR?
1 Like
It is merged. Thanks for your contribution!