Material.opacity set to NaN after animation stops

Good day, Community!

I am having trouble animating the material.opacity of meshes in my application.

The AnimationClip which I use includes color animation which works perfectly. Opacity also animates correctly, but once the animation is completed the material’s opacity is set to NaN, making the mesh invisible in the scene.

Opacity is animated using a NumberKeyframeTrack. I have inspected the AnimationKeyframe and AnimationAction in the console but found no obvious problems. I have compared my code to many examples and find no obvious discrepancies.

Unfortunately I cannot provide a codepen or give a meaningful extract of the code. I am hoping someone has experienced a similar issue before?

Even just a creative suggestion on how to troubleshoot this problem would be very welcome.

The following animation updates the material’s opacity. Do you mind modifying it in order to demonstrate the issue?

@Mugen87 thank you for the suggestion.

Here is the modified fiddle:

You will see that the mesh has a correct material opacity of 1 after construction.
However, when you click the stop button the opacity becomes NaN.
This does not prevent the animation from running correctly if you play it again.

@Mugen87 I can set the material.opacity “manually” to a valid number after stopping the animation, making the fiddle work correctly.

However, in my application the target of the AnimationKeyFrames are created dynamically at runtime by the user. Handling the material.opacity properties as an exception (resetting it to a valid value after animation has run) would add unwanted complexity.

I have done some further investigation and narrowed down the problem. It is something of a rabbit hole, but here is what i found.

When play() is called on the AnimationAction instance, it results in saveOriginalState() being called on all the action’s PropertyMixer objects. The purpose of this is to save the state of all the bound object properties in an internal buffer array.

When stop() is called on the AnimationAction instance, it results in restoreOriginalState() being called to restore the state of the bound objects from the internal buffer array.

By writing the buffer arrays to the console, I found that it is not correctly populated for the “.material.opacity” animation, instead an original state value of NaN is written to the buffer. Therefore, upon completion of the animation when restoreOriginalState is called, NaN is the result.

This is not the case for the “.material.color” property for which the buffer arrays are correctly populated and for which the original state is correctly restored after animation has stopped.

The difference between the “.material.color” and “.material.opacity” is that the former results in a BindingType of “EntireArray”, while the latter results in a BindingType of “Direct”. This ultimately results the saveOriginalState() calling one of the functions below on the corresponding PropertyBinding. Here getValue_direct is not working as expected, but getValue_array is working as expected.

> function getValue_direct( buffer, offset ) {
> 	buffer[ offset ] = this.node[ this.propertyName ];
> },
> function getValue_array( buffer, offset ) {
> 	const source = this.resolvedProperty;
> 	for ( let i = 0, n = source.length; i !== n; ++ i ) {
> 		buffer[ offset ++ ] = source[ i ];
> 	}
> }
1 Like

Thanks for the investigation. I think I could come up with a fix:

@Mugen87 Investigating is half the fun. :grinning:

However, although I understand the fix in the source code, what are my best options now as a workaround until I can move to a version of the source code containing the fix…? Your advice would be greatly appreciated as I face a looming deadline.

It seems you have to restore the original value manually. Otherwise you have to use a modified version of three.js (by manually including the above patch).

Thank you for the solid advice as always. I have opted to use a modified version of three.js, and simply documented the change for now. It will become a non-issue in future, so there is no real risk.

1 Like