3ds Max->glTF (exporter v20220401.2) Animated rotation interpolation issue

We’ve been having an issue where some animations exported from 3ds Max (exporter version v20220401.2) and imported into Unity (with a custom importer) glitch seemingly randomly on rotating objects. As an example, this is a test object of a simple pendulum swinging back and forth (Time.timeScale set to 0.1 to more clearly show the issue):

issue

For reference, this is the source animation in 3ds Max:

In investigating this, I extracted the keyframe data and plotted it on a graph.

As you can see, when the w component passes 0.5, both the x and w components flip to negative, then back to positive when w passes -0.5 again. Because Unity is interpolating between the values before and after the flip, it causes the object to rapidly (over just a couple of frames) spin through 360 degrees.

If I go into the exported GLB and manually change all the negative values back into positive, this does fix the glitch:

To further complicate the issue, this flipping doesn’t seem to consistently happen. In another test, we have 3 spheres with each rotating 360 degrees around a different cardinal local axis. The resulting GLB’s all exhibit the same basic behaviour, but at slightly different times despite w component of the rotation being effectively identical, and only at one of the instances of crossing 0.5:

This seems to me to be a bug in the exporter, and even seems to be something covered in the glTF specification (glTF 2.0 Specification - Cubic Interpolation), albeit in relation to actual cubic keyframes as opposed to the way the exporter bakes the animation down to linear keyframes.

1 Like

cc @Guillaume_Pelletier

Hello, thanks for the hint.
I already seen that once and conclude at this time we might have an floating point presision error with our quaternion operations.
I will work on the animation soon and this question is related to this issue (Optimize Animation not really optimizing · Issue #1043 · BabylonJS/Exporters · GitHub). If you can send us a simple scene that highlights this behavior, it will be very useful for us.

Thanks.

1 Like

Thanks for the reply.

Good to hear that a fix is in the pipeline. As requested, here is a reproduction case.

Out of interest, do you have an order-of-magnitude guess (i.e. days/weeks/months) as to when a fix might be available? I should be able to throw together a quick hack to mitigate the issue in the short term, but might need something more robust if it’s on the longer end.

Thanks,
Stephen

1 Like

Hello,
I investigated a bit this issue and found that the inversion seems to come from the “duality” of quaternion, meaning that any given rotation has two possible quaternion representations, and then the sign is ambigous.
If one is known, the other can be found by taking the negative of all four terms. This has the effect of reversing both the rotation angle and the axis of rotation. So for all rotation quaternions, ( q 0, q 1, q 2, q 3) and (− q 0, − q 1, − q 2, − q 3) produce identical rotations.

In the strictly mathematical sense, and under Babylon, the rotation is totally legit, but apparently may conduct to dysfunction with other gltf reader, such unity.

I will investigate into the math we use into matrix decompose to see if my assumption is right and then see if another algorithm may produce “better” result.

regards

EDIT:
I found a simple solution to solve the continuity.


I add this piece of code to test the distance between the two consecutive quaternion

                   ...
                    tm_babylon.decompose(s_babylon, q_babylon, t_babylon);

                    if (q_previous != null)
                    {
                        // avoid jump 
                        // flip the sign of the current quaternion if it makes the distance between the quaternions in the 4D vector space smaller.
                        // any given rotation has two possible quaternion representations, and then the sign is ambigous.
                        // If one is known, the other can be found by taking the negative of all four terms.This has the effect of reversing both the rotation
                        // angle and the axis of rotation.So for all rotation quaternions, (q 0, q 1, q 2, q 3) and(− q 0, − q 1, − q 2, − q 3) produce identical rotations
                        if ((q_babylon - q_previous).SquaredNorm() > (q_babylon + q_previous).SquaredNorm())
                        {
                            q_babylon = -q_babylon;
                        }
                    }
                    q_previous = q_babylon;

Explanation are in the comment into the code. Operator override and some functions are also added to Quaternion class.

@Deltakosh, @stephen_mxr , Guy’s, Let me know what you’re thinking about.

test.zip (19.1 KB)

G.

1 Like

Hmm, this is an interesting one. Yes, all quaternion values have an equivalent negative sibling value. I believe if the interpolation is slerp as noted in the glTF spec, then it shouldn’t matter which quaternion is used. I wonder if Unity is not using Slerp for the interpolation of quaternions?

@stephen_mxr In your custom Unity importer, are you using Quaternion Interpolation as noted here? I believe this mode will cause Unity use Quaternion.Slerp. If not, that might be the cause of the problem.

That said, I’m not opposed to the fix in the exporter. Having closer quaternion values isn’t a bad thing.

2 Likes

I don’t think that specific property is exposed anywhere when creating animations programmatically. The rotations in the animation are created as quaternions:

AnimationCurve[] curves = CreateQuaternionAnimationCurves(animation.Samplers[channel.Sampler]);

clip.SetCurve(path, typeof(Transform), "localRotation.x", curves[0]);
clip.SetCurve(path, typeof(Transform), "localRotation.y", curves[1]);
clip.SetCurve(path, typeof(Transform), "localRotation.z", curves[2]);
clip.SetCurve(path, typeof(Transform), "localRotation.w", curves[3]);

and the animation window does have “Quaternion Interpolation” clicked after its imported.

While looking around for that interpolation property, I did come across the clip.EnsureQuaternionContinuity() function that I’d not seen before, and which appears (from the 2 test files I’ve looked at so far) to fix my issue!

Thanks a lot for your replies!

1 Like

the animation window does have “Quaternion Interpolation” clicked after its imported.

If so, maybe the export fix is required or Unity is not using Quaternion.Slerp internally. @Guillaume_Pelletier Does exporting this scene prior to your fix work in Babylon.js?

clip.EnsureQuaternionContinuity()

I found this article talking about this function which seems to be exactly your issue. :slight_smile:

How can I set Animation Curve Interpolation via Scripting? – Unity

Animation was fine into Babylon, I propose the “fix” to get more friendly curves for people who do not know about quaternion theory.

1 Like

I guess Unity is not using Slerp internally then.