AnimationUtility.SetEditorCurve throws QuaternionToEuler: Input quaternion was not normalized

Hi.

I’m working on an animation import tool for our artist so he can create a native Unity animation out of set of xml files and a spritesheet that his animation software exports.

I’ve got a strange errors though, that are caused by this line of code AnimationUtility.SetEditorCurve(animationClip, rotationW, curveW);

1st error:
QuaternionToEuler: Input quaternion was not normalized
UnityEditor.AnimationUtility:SetEditorCurve(AnimationClip, EditorCurveBinding, AnimationCurve)
2nd error:
Assertion failed on expression: 'IsFinite(rot)'
UnityEditor.AnimationUtility:SetEditorCurve(AnimationClip, EditorCurveBinding, AnimationCurve)

They happen when I try to set the rotation via AnimationCurve. They are caused by setting the curve for a .w value of a quaternion.

This is my code:

var rotationX = GetBinding("rotation.anglex", path);
var rotationY = GetBinding("rotation.angley", path);
var rotationZ = GetBinding("rotation.anglez", path);
var rotationW = GetBinding("rotation.anglew", path);

var pts = timedValues[attrlink.Timedvalue];

List<Keyframe> keyframesX = new List<Keyframe>();
List<Keyframe> keyframesY = new List<Keyframe>();
List<Keyframe> keyframesZ = new List<Keyframe>();
List<Keyframe> keyframesW = new List<Keyframe>();

for (int i = 0; i < pts.Count; i++)
{
    var time = i / animationClip.frameRate;
  
    var keyframeX = new Keyframe();    
    var keyframeY = new Keyframe();   
    var keyframeZ = new Keyframe();     
    var keyframeW = new Keyframe();

    keyframeX.time = time;
    keyframeY.time = time;
    keyframeZ.time = time;
    keyframeW.time = time;

    //The xml stored values are in radians.
    var degree = float.Parse(pts[i].Y) * Mathf.Rad2Deg;
    var euler = new Vector3(0, 0, degree);
    var quaternion = Quaternion.Euler(euler);

    keyframeX.value = quaternion.x;
    keyframeY.value = quaternion.y;
    keyframeZ.value = quaternion.z;
    keyframeW.value = quaternion.w;
  
    keyframesX.Add(keyframeX);
    keyframesY.Add(keyframeY);
    keyframesZ.Add(keyframeZ);
    keyframesW.Add(keyframeW);

}

var curveX = new AnimationCurve(keyframesX.ToArray());
var curveY = new AnimationCurve(keyframesY.ToArray());
var curveZ = new AnimationCurve(keyframesZ.ToArray());
var curveW = new AnimationCurve(keyframesW.ToArray());

for (var i = 0; i < curveX.keys.Length; i++)
{
    AnimationUtility.SetKeyLeftTangentMode(curveX, i, AnimationUtility.TangentMode.Constant);
    AnimationUtility.SetKeyRightTangentMode(curveX, i, AnimationUtility.TangentMode.Constant);
}

for (var i = 0; i < curveY.keys.Length; i++)
{
    AnimationUtility.SetKeyLeftTangentMode(curveY, i, AnimationUtility.TangentMode.Constant);
    AnimationUtility.SetKeyRightTangentMode(curveY, i, AnimationUtility.TangentMode.Constant);
}

for (var i = 0; i < curveZ.keys.Length; i++)
{
    AnimationUtility.SetKeyLeftTangentMode(curveZ, i, AnimationUtility.TangentMode.Constant);
    AnimationUtility.SetKeyRightTangentMode(curveZ, i, AnimationUtility.TangentMode.Constant);
}

for (var i = 0; i < curveW.keys.Length; i++)
{
    AnimationUtility.SetKeyLeftTangentMode(curveW, i, AnimationUtility.TangentMode.Constant);
    AnimationUtility.SetKeyRightTangentMode(curveW, i, AnimationUtility.TangentMode.Constant);
}
AnimationUtility.SetEditorCurve(animationClip, rotationX, curveX);
AnimationUtility.SetEditorCurve(animationClip, rotationY, curveY);
AnimationUtility.SetEditorCurve(animationClip, rotationZ, curveZ);
AnimationUtility.SetEditorCurve(animationClip, rotationW, curveW);

I also did some debugs for the final curve and it looks like all the values are set correctly.

Setting Rotation Curve for following clip: Idle_Top
Rotation X:
Path: Idle1_Up_UP_Body-P/Idle1_Up_head PropertyName: m_LocalRotation.x Type: UnityEngine.Transform IsDiscrete False IsPPtr False
Curve X Length: 10
Curve X KeyFrame[0] Time: 0 Value: 0
Curve X KeyFrame[1] Time: 0.125 Value: 0
Curve X KeyFrame[2] Time: 0.25 Value: 0
Curve X KeyFrame[3] Time: 0.375 Value: 0
Curve X KeyFrame[4] Time: 0.5 Value: 0
Curve X KeyFrame[5] Time: 0.625 Value: 0
Curve X KeyFrame[6] Time: 0.75 Value: 0
Curve X KeyFrame[7] Time: 0.875 Value: 0
Curve X KeyFrame[8] Time: 1 Value: 0
Curve X KeyFrame[9] Time: 1.125 Value: 0
Rotation Y:
Path: Idle1_Up_UP_Body-P/Idle1_Up_head PropertyName: m_LocalRotation.y Type: UnityEngine.Transform IsDiscrete False IsPPtr False
Curve Y Length: 10
Curve Y KeyFrame[0] Time: 0 Value: 0
Curve Y KeyFrame[1] Time: 0.125 Value: 0
Curve Y KeyFrame[2] Time: 0.25 Value: 0
Curve Y KeyFrame[3] Time: 0.375 Value: 0
Curve Y KeyFrame[4] Time: 0.5 Value: 0
Curve Y KeyFrame[5] Time: 0.625 Value: 0
Curve Y KeyFrame[6] Time: 0.75 Value: 0
Curve Y KeyFrame[7] Time: 0.875 Value: 0
Curve Y KeyFrame[8] Time: 1 Value: 0
Curve Y KeyFrame[9] Time: 1.125 Value: 0
Rotation Z:
Path: Idle1_Up_UP_Body-P/Idle1_Up_head PropertyName: m_LocalRotation.z Type: UnityEngine.Transform IsDiscrete False IsPPtr False
Curve Z Length: 10
Curve Z KeyFrame[0] Time: 0 Value: 0
Curve Z KeyFrame[1] Time: 0.125 Value: 0.01776127
Curve Z KeyFrame[2] Time: 0.25 Value: 0.0355178
Curve Z KeyFrame[3] Time: 0.375 Value: 0.0355178
Curve Z KeyFrame[4] Time: 0.5 Value: 0.0355178
Curve Z KeyFrame[5] Time: 0.625 Value: 0.0355178
Curve Z KeyFrame[6] Time: 0.75 Value: 0.0355178
Curve Z KeyFrame[7] Time: 0.875 Value: 0.0355178
Curve Z KeyFrame[8] Time: 1 Value: 0.01776127
Curve Z KeyFrame[9] Time: 1.125 Value: 0
Rotation W:
Path: Idle1_Up_UP_Body-P/Idle1_Up_head PropertyName: m_LocalRotation.w Type: UnityEngine.Transform IsDiscrete False IsPPtr False
Curve W Length: 10
Curve W KeyFrame[0] Time: 0 Value: 1
Curve W KeyFrame[1] Time: 0.125 Value: 0.9998422
Curve W KeyFrame[2] Time: 0.25 Value: 0.999369
Curve W KeyFrame[3] Time: 0.375 Value: 0.999369
Curve W KeyFrame[4] Time: 0.5 Value: 0.999369
Curve W KeyFrame[5] Time: 0.625 Value: 0.999369
Curve W KeyFrame[6] Time: 0.75 Value: 0.999369
Curve W KeyFrame[7] Time: 0.875 Value: 0.999369
Curve W KeyFrame[8] Time: 1 Value: 0.9998422
Curve W KeyFrame[9] Time: 1.125 Value: 1

I get double the amount of keyframes of these both errors for each Curve W i’m trying to set. These errors happen if I use the AnimationUtitlity.SetEditorCurve or animationClip.SetCurve methods. Whats strange is, that despite these errors, the animationClip seems to be working correctly, setting the rotation for each frame as it should be. But still, I’d like to get rid of these errors anyway.

Quaternion components x,y,z,w are NOT angles. Read about it on any Quaternion tutorial.

If you want to use Euler angles to create a Quaternion, you can use the factory method:

var myQuat = Quaternion.Euler( angleX, angleY, angleZ);

Generally there is never a call to touch the .x, .y, .z and .w components of Quaternion

Have you read my post or my code? This is exactly what I’m doing…

I’m converting the euler angles into a quaternion value for each frame and then setting all of the four values calculated by the very method you’ve quoted via the SetCurve methods as the AnimationCurve as shown by
@seant_unity in this thread - https://discussions.unity.com/t/733276 .

I saw two things, the first being this:

var rotationX = GetBinding("rotation.anglex", path);
var rotationY = GetBinding("rotation.angley", path);
var rotationZ = GetBinding("rotation.anglez", path);
var rotationW = GetBinding("rotation.anglew", path);

… semantically conflating angle and x,y,z,w members. Sure, just string property/variable names, but it seemed potentially incorrect, a common source of confusion with Quaternions.

And the second being that you’re getting a normalization error, which I presume is an internal guard on quaternions handed into the animation system so they are close to normalized. Just because two keyframes are normalized does not in any way mean every point between them is normalized, obviously.

Finally, now quickly making and looking in an animation YAML file that curves my DirectionalLight from -30 to -76 degrees local x rotation, I see that the curves are not stored as curves of x,y,z,w terms but rather Euler angles:

path:
classID: 4
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 2
time: 0
value: -30
inSlope: 0
outSlope: 0
tangentMode: 136
- serializedVersion: 2
time: 1
value: -76.78
inSlope: 0
outSlope: 0
tangentMode: 136
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: localEulerAnglesRaw.y```

This all would seem that you are driving something that does not exist, a "euler w," since that is the line complaining.

Now, having said that, I've not tinkered with that the AnimationUnity you're using, but if it all works except the error, well, excellent... go ahead. All I saw in my animation curve was bindings for x,y,z local Euler angles, which would imply that's the space over which the editor curves apply.

These “rotation.anglex” names are internal to the animation software that i’m working with.The code I’ve posted is from a very rough prototype I’ve made as a proof of concept that it can be done (the rest of a code is not pretty at all as well). This method basically returns this for the ‘w’ property.

EditorCurveBinding binding = new EditorCurveBinding();
binding.path = path;
binding.type = typeof(Transform);
binding.propertyName = "m_LocalRotation.w";

From the thread I’ve linked it’s stated that - The animation system uses quaternions as their representation, and does it's best to show the corresponding euler angle in the inspector..

And this makes absolute sense because I need to animate the m_LocalRotation and that’s a quaternion. And in order to create the animation curve you need to bind the curve to a specific property in a target component. So I can’t animate euler angles. The property you’re showing looks like it may be some cache value for the inspector, but I can’t bind to this value because it’s in a AnimationClip itself and the binding needs to be for property in a target object.

And as I stated in my original post - this code works. The animation has correct rotation values for all frames. I just get these errors for some reason. They are not breaking the code, it executes completely and finishes the function but it just shows these errors.

Have a look at this post. So using EnsureQuaternionContinuity might help.

@Bunny83

Ok, I’ve tried this method and found out some stuff, that eventually lead me to a solution.

It helps with the errors, but while doing so it changes the TangentMode from Constant which smoothes the animation - and for my use case that’s not a desired behaviour.

But it gave me an idea to look at the curves itself (i was only checking out the dopesheet before) and it looks like that it’s not the curveW (contrary to the console showing that it was caused by this line of code), that was causing these particular errors were the X and Y (angle) values.

So I’ve decided to remove all of the SetKeyTangent methods (I’ve only removed it for the curveW before) and check if the errors will be still there and they were gone. But again this leaves me with a smooth animation so that was not a valid solution for me.

This put me on a quest of looking for information about setting constant rotation frames from script and I’ve found out that constant rotation frames are actually broken since Unity version 5 - Rotation animation issue with constant tangents? .

In this same thread there was a link to this tool - GitHub - unity-cn/CustomAnimationTools. This tool basically goes through animation clip properties manually and sets them to correct values for an animation that is supposed to have constant, not interpolated frames.

So I’ve simply exposed a method from this tool so I can just pass the animation clip I’ve just create. This converts my animation with default, smooth interpolation between frames into a constant animation.

I’ll accept this as a solution to my problem. Thank you all for your help and input.

3 Likes