KhronosGroup / glTF

glTF – Runtime 3D Asset Delivery
Other
7.12k stars 1.13k forks source link

Rotation animation should be stored using Euler not Quaternions #1515

Closed greggman closed 5 years ago

greggman commented 5 years ago

AFAIK all animation packages, Maya, 3DSMax, Blender, etc store there rotation data as Euler rotations.

This is important because AFAIK Quaternions have no concept of spins larger than 1/2 a sphere. With Euler I can make an animation that spins around 4 times by setting just 2 keys. Key #1 = 0 degrees, Key #2 = 1440. With Quaternions AFAIK this can only be done by sampling the animation at some arbitrarily high sample rate where the sample rate must be high enough that no 2 keys rotate more than 180 degrees.

ziriax commented 5 years ago

Although glTF doesn't support this, it is possible to extend quaternions with the number of complete spins that should be taken.

See for example this Maya plugin https://github.com/yantor3d/quatExtras/wiki/Quaternion-Slerp-Node

Euler angles come with a lot of problems on their own, like gimbal locking and many angles representing the same orientation, so I don't think Euler angles should be the default, but maybe that's just me.

greggman commented 5 years ago

Gimble lock and many angles representing the same orientation is irrelevant to animation. Artists create the animations using Eulers. The data starts in Eulers. Switching that data to quaternions loses data.

There are appropriate places to use Quaterions but animation data is arguably not one of them.

Examples of systems using Eulers for rotation animation

Unreal:

unreal-rotation

Unity:

unity-rotation

Godot:

godot-rotation

Blender:

blender-rotation

If I had copies of Maya and 3DSMax I could show those use Eulers for animation as well.

ziriax commented 5 years ago

I certainly agree that Euler angles should be an option (together with rotation axis order), but when it comes to exporting a complex animated rig from say Maya to glTF, constraints such as inverse kinematics, multiple parent constraints, orientation constraints, expressions, etc often make it so that there is no correlation anymore between the original Euler animation curves, and the final orientations.

glTF is a bit like JPEG, it is an export format, not an authoring format. I guess you are authoring glTF directly then? What exporter are you using?

Anyway, just to make this clear, I'm not part of the glTF group, I'm just following it.

greggman commented 5 years ago

I don't think it should be an option, I think it should be the only supported format. I think nodes should have rotation in Eulers for gLTF 3.0 and I think animation should store it's keys in Eulers. This is the format nearly all engines use. I showed 3 engines above that use this format. They may convert Eulers to Quaternions internally when mixing in IK but they store data in Eulers. I've worked on 4 other engines at Sega, Sony, Namco, and Google that all used Eulers. That data that is important at runtime.

Even artist defined constraints are expressed in Eulers.

I get the difference between runtime data and authoring data. I've written the entire export pipeline for 6 games.

I'm not authoring gLTF directly. I'm trying to use it and finding it's missing data I've used all those games I've worked on. I can write my own format which is what I've always had to do but I'm here trying to make glTF useful and better.

It harms nothing to store the rotation in Eulers. An engine that wants Quaternions can just covert at load time and lose the data. An Engine that needs to know the game designer marked one node as rotation: [0, 0, 0] and another as [0, PI * 6, 0] can keep it in Eulers. This is no different than pointing out that converting all textures to JPEG would be unaccaptable as well since JPEG is lossy and JPEG is only 3 channels. JPEG is designed for photos but textures are not photos. Textures can be normal maps, specular maps, height maps, ambient occlusion maps, etc. Losing data there is unacceptable. Similarly, losing the fact that a rotation is supposed to spin N times in a specific direction OR that the rotation is actually 6.1π and not 0.1π is just as important.

donmccurdy commented 5 years ago

This is the format nearly all engines use. I showed 3 engines above that use this format. They may convert Eulers to Quaternions internally when mixing in IK but they store data in Eulers.

I'm not sure the fact that the editors display Eulers is necessarily a clear indication that they're storing it that way... from Unity's docs, the situation seems more complicated:

Unity uses Quaternions internally, but shows values of the equivalent Euler angles in the inspector to make it easy for you to edit.

In Unity all Game Object rotations are stored internally as Quaternions, because the benefits outweigh the limitations... As a side-effect, it is possible in the inspector to enter a value of, say, X: 0, Y: 365, Z: 0 for a Game Object’s rotation. This is a value that is not possible to represent as a quaternion, so when you hit Play you’ll see that the object’s rotation values change to X: 0, Y: 5, Z: 0 (or thereabouts). This is because the rotation was converted to a Quaternion which does not have the concept of “A full 360-degree rotation plus 5 degrees”, and instead has simply been set to be oriented in the same way as the result of the rotation.

And about Animation specifically:

When importing animation from external sources, these files usually contain rotational keyframe animation in Euler format. Unity’s default behaviour is to resample these animations and generate a new Quaternion keyframe for every frame in the animation, in an attempt to avoid any situations where the rotation between keyframes may exceed the Quaternion’s valid range.

... There are still some situations where - even with resampling - the quaternion representation of the imported animation may not match the original closely enough, For this reason, in Unity 5.3 and onwards there is the option to turn off animation resampling, so that you can instead use the original Euler animation keyframes at runtime.

^I'm not copying all of this to argue glTF should remain quaternions-only — I'm really not sure. But if engines like Unity have decided to resample Eulers as quaternions by default, it would be helpful to understand why that is. 😕

emackey commented 5 years ago

I'll point out that new users can run into this pretty easily. For example, the first time someone goes to animate a wheel in Blender, they typically put keyframes at 0 and 360 degrees, with linear interpolation. These both convert to the same quaternion, so in the glTF the wheel doesn't move at all.

One possible quick-fix is for the Blender exporter to detect when rotation between keyframes is >= 180 degrees and automatically resample, as Unity does. But storing native Eulers probably should be on our list to consider for glTF next.

ziriax commented 5 years ago

If that is the case, then the exporter is wrong IMHO.

IMHO Euler makes sense for rotation about a single axis only (and that could be represented with an angle axis representation too, to have the advantages of both Euler and quaternions). Otherwise you open a can of worms, order of rotation needs to be specified, gimbal locks are introduced, ...

StephenHodgson commented 5 years ago

Quaternion > Euler

greggman commented 5 years ago

This is the format nearly all engines use. I showed 3 engines above that use this format. They may convert Eulers to Quaternions internally when mixing in IK but they store data in Eulers.

I'm not sure the fact that the editors display Eulers is necessarily a clear indication that they're storing it that way... from Unity's docs, the situation seems more complicated:

Those docs just confirm my point. Unity wrongly chose quaternions, ran into the issues I pointed out, and had to go add Euler back in. From the docs

Unity’s Animation Window

Within Unity’s own animation window, there are options which allow you to specify how the rotation should be interpolated - using Quaternion or Euler interpolation. By specifying Euler interpolation you are telling Unity that you want the full range of motion specified by the angles. With Quaternion rotation however, you are saying you simply want the rotation to end at a particular orientation, and Unity will use Quaternion interpolation and rotate across the shortest distance to get there/

All even point out that while Unity has the open to use Quaternions for animation the default is Euler

In other words, Euler is what artists want, it's what they expect, it's what 3rd party tools use, it produces the correct animations, Unity effed up picking Quats and had to go add the option in to make things work. glTF should not follow down their mistaken path.

greggman commented 5 years ago

Otherwise you open a can of worms, order of rotation needs to be specified, gimbal locks are introduced

Gimbal lock is not an issue with animation because artists make their animations and play them back in their editor. If they see a gimbal lock type of issue they fix it before exporting. Gimbal lock is only an issue with code generated rotations like IK which is why you take the artists Euler animation data, animate it in Euler, and if you need to merge it with something else convert to Quaternions and do the merge.

It's also not a can of worms, it's what you need to do to get what your artists created. As pointed out in the unity docs

There are still some situations where - even with resampling - the quaternion representation of the imported animation may not match the original closely enough, For this reason, in Unity 5.3 and onwards there is the option to turn off animation resampling, so that you can instead use the original Euler animation keyframes at runtime.

In other words, Euler gives correct results if your goal is to display what the artist created.

StephenHodgson commented 5 years ago

No matter how much you argue here, the simple matter is, objectively it's much easier to do math with, and utilize quaternions in 3d graphics.

ziriax commented 5 years ago

Otherwise you open a can of worms, order of rotation needs to be specified, gimbal locks are introduced

Gimbal lock is not an issue with animation because artists make their animations and play them back in their editor. If they see a gimbal lock type of issue they fix it before exporting. Gimbal lock is only an issue with code generated rotations like IK which is why you take the artists Euler animation data, animate it in Euler, and if you need to merge it with something else convert to Quaternions and do the merge.

For storing the animated curves themselves, yes, I agree that in some cases (especially when rotating about a single axis), Euler angles can provide useful information at runtime. I actually never needed it as far as I recall, I always treated animations as black-boxes, not caring how internally the engine handled the rotations, but I agree that not having Euler angles might be a problem for some.

It's also not a can of worms, it's what you need to do to get what your artists created. As pointed out in the unity docs

There are still some situations where - even with resampling - the quaternion representation of the imported animation may not match the original closely enough, For this reason, in Unity 5.3 and onwards there is the option to turn off animation resampling, so that you can instead use the original Euler animation keyframes at runtime.

In other words, Euler gives correct results if your goal is to display what the artist created.

That is true. It doesn't apply to our GLTF usage, since our rigs use IK and constraints in Maya, so that the original Euler rotations do not match the final output rotations anyway, but I certainly can see use cases for games where a one-to-one match between the artist Euler angles and engine rotations is desired.

The problem is, if one tries to please the whole world, one pleases nobody, since the spec becomes complicated, and we already have Pixar's USD for that ;-)

greggman commented 5 years ago

No matter how much you argue here, the simple matter is, objectively it's much easier to do math with, and utilize quaternions in 3d graphics.

Agreed 100%. That has absolutely nothing to do with my point whatsoever.

That is true. It doesn't apply to our GLTF usage, since our rigs use IK and constraints in Maya, so that the original Euler rotations do not match the final output rotations anyway, but I certainly can see use cases for games where a one-to-one match between the artist Euler angles and engine rotations is desired.

The problem is, if one tries to please the whole world, one pleases nobody, since the spec becomes complicated, and we already have Pixar's USD for that ;-)

It's trivial for you to convert Euler to Quaternion when you load if that's what you want. It is impossible to go the other way as the conversion is lossy. So, put Eulers in the file and you get both what you need and what artists needed.

ziriax commented 5 years ago

Yes, I agree, but if only Euler angles are supported, then an exporter that samples each frame (like our Maya2glTF) will have to pick a consistent set of Euler angles to represent each sampled orientation, in such a way that the change between two frames is minimal, since infinitely many different Euler angles represent the same orientation. Annoying, but I guess possible to do, although I would prefer to have support for both quaternions and Eulers...

So in the end, after a lot of debate, you convinced me :-)

andreasplesch commented 5 years ago

I suspect quaternions were chosen because an engine in the end has to use them or matrices anyways. As a transmission format glTF is geared towards being friendly towards machines rather than artists. By now, however, glTF may become more of a universal format.

greggman commented 5 years ago

Yes, I agree, but if only Euler angles are supported, then an exporter that samples each frame (like our Maya2glTF)

Sampling has other problems. In particular you can't have a discontinuous keys. With non sampled animations I can make keys like this

  time: 0:00s   value: 0deg
  time: 1:00s   value: 90deg
  time: 1:00s   value: 180deg
  time: 2:00s   value: 270deg

Notice there are two entries for 1:00 second. Say this is a camera's rotation. You want to rotate from 0 to 90 over one second, then snap to 180, and rotate to 270 over 1 second.

Imagine you sample it at 60fps so let's change it to frames. The first problem you have is what goes at frame 60? 180 or 90? I'm guessing your sampler picks 180 so you get

 frame: 58   value: 87
 frame: 59   value: 88.5
 frame: 60   value: 180
 frame: 61   value: 181.5

Now Imagine you have have an app that lets you slow down time (bullet time from the matrix). Your code will end up interpolating between frames 59 and 60 showing parts of the scene your artist never intended to show.

It doesn't actually require a slowdown in time to have this issue. All it requires is that you interpolate between frames at all and for the time calculate out to frame 59.5 and suddenly you'll have an unwanted flash in your game as the camera shows that bright billboard the artist was trying to skip. It get's worse if it was a translation key and for a frame the camera/object is in the middle of some nearly random space instead of the 2 places the artist keyed it.

ziriax commented 5 years ago

Good point. I am going to add a feature to Maya2glTF to deal with this.

Off topic, but since you seem to know so much about this, if you happen to know a good free library to fit piecewise cubic monotonic curves to sampled data, I can't seem to find one. Personally I would fit Daubechies or similar wavelets to sampled data to perform animation compression, but GLTF only supports Hermite curves...

greggman commented 5 years ago

Sorry I don't have any curve fitting algorithms to suggest. My experience comes mostly from running into these types of problems the hard way. Using quats or even truncated Eulers for compression (angle % 360) and then finding out it didn't work for files my artists gave me. Using sampling and again finding out my artists made stuff that didn't work.

As for why quaternions are in glTF to start my guess is that whoever wrote the spec really only considered character animation which is generally a place none of these issues come up. They come up more in non-character animation. Animating a camera. An entire scene. A control panel in a space station, clocks, gears, dials, environmental effects, A 2D animation that happens to be made using a 3D system. etc...

scurest commented 5 years ago

Cf #144?

AFAIK all animation packages, Maya, 3DSMax, Blender, etc store there rotation data as Euler rotations.

I only know about Blender, but you actually have a choice of quaternion, axis-angle, or six different Euler angle orders (XYZ, XZY, etc.). What order do you want for the Euler angles? How easy is it to convert from one order to another?

This is important because AFAIK Quaternions have no concept of spins larger than 1/2 a sphere.

Depends on #1395 whether they can go 1/2 or 1.

Gimble lock and many angles representing the same orientation is irrelevant to animation. Artists create the animations using Eulers. The data starts in Eulers. Switching that data to quaternions loses data.

Well, obviously not all data starts in Eulers. Switching to Eulers makes other kinds of inputs harder. I have some files that start in rotation matrices and switching to quaternions is trivial. What happens if it starts in Eulers, but with a different order? Is switching lossless?

It's trivial for you to convert Euler to Quaternion when you load if that's what you want.

It doesn't seem to be trivial to convert Euler->Quaternion (that's why the exporter does it wrong!). Because as you point out, a single keyframe-pair in Euler angles can go through an arbitrarily long path in SO(3) but with quaternions you can only go through the shortest path. So to get an equivalent path you would need to subsample the time domain enough times that each segment is short enough to be represented by quaternion slerp. For angles with very high numbers of rotations (eg. from 0 to 2Nπ for large N) over a very small time period, it is even possible for the Euler angle path to be unrepresentable with quaternions on account of there being insufficiently many floating point numbers in the interval to make enough segments, although in practice this seems unlikely.

Going Quaternion->Euler doesn't seem to require subsampling but you'd need to avoid gimbal lock somehow I guess. Do you know how to do that?

It is impossible to go the other way as the conversion is lossy.

Both of these directions are lossy (Euler->Quaternion->Euler might result in angles shifted by a multiple of 2pi and it might lose the original timeline information due to requiring subsampling for long paths and Quaternion->Euler->Quaternion might result in quaternions with different signs).


To lerp Euler angles I assume we lerp each angle independently? I don't think this results in motion along a geodesic in SO(3) like slerping quaternions does, does it? Do we know how much the path can differ if we do interpolation using Euler angles vs using quaternions?

recp commented 5 years ago

Isn't it possible to go along long path with quaternion?

https://en.wikipedia.org/wiki/Slerp (Source code section):

// If the dot product is negative, slerp won't take
// the shorter path. Note that v1 and -v1 are equivalent when
// the negation is applied to all four components. Fix by 
// reversing one quaternion.
if (dot < 0.0f) {
  v1  = -v1;
  dot = -dot;
}

maybe if we skip these lines if angle is greater than PI (180deg) then we can go along long path (I'm not sure yet)? If this is possible I can implement alternative SLERP function[s] in cglm and in my render engine. In this case we would not need to alternative rotation representations?

scurest commented 5 years ago

It depends on #1395. But even if you ignore those lines you still can't go >= 1 full rotation using quaternions, while with Euler angles you can go arbitrarily many (eg. 0 to 4π will rotate twice).

recp commented 5 years ago

If one full rotation could be possible by ignoring those lines by a condition then rotating twice or more could be done by adding a property like repeatCount to animation or channel object. I think using repeatCount is better than specifying angle[s] as 4π/8π... My engine uses this property to repeat animation[s] animation.h#L69 and this makes things easier to maintain (for me at least)

ziriax commented 5 years ago

Yes, see the second reply in this topic, with an example of a Maya plugin.

However, it is not obvious how one would convert from Euler angles to such a quaternion + spins aka revolutions aka “repeatCount”. If an artists sets keys in Euler angles, the rotation path taken will be different than the one you get by converting to quaternions + spins...

So this extra spins property is only useful when artists use this in their animation software directly, something that is not very standard practice imho.

donmccurdy commented 5 years ago

Pulling this reply over from #1518 —

Unity considers [quaternions] a wart. Their animation system defaults to Eulers and as pointed out in the other thread they needed to support Eulers to actually handle animations users were created in external packages.

I don't yet see solid evidence for this — if Unity wanted to change their internal representation, they could. Again from the docs, In Unity all Game Object rotations are stored internally as Quaternions, because the benefits outweigh the limitations. Clearly there are warts involved with doing the conversion, and because artists reasonably need to work with Eulers, that conversion needs to happen somewhere. But if authoring tools all use Eulers (clearly true), and most engines interpolate on Quaternions (also appears to be true), then there's a case to be made that the lossy conversion should happen before writing to glTF, so that what's left is more likely to be interpreted consistently across engines. In general that is part of the ethos of the format, although there can of course be exceptions.

Again, I don't have my mind made up about this suggestion yet, but I don't think it's such a clear open-and-shut case.

greggman commented 5 years ago

There are 2 distinct issues here

  1. What format the data is in the glTF file

  2. What your particular engine does with that data

This topic is about the data format, NOT your engine. If your engine wants to use quaternions that's all fine. Load the Euler's convert to quats however you want. Handle multiple spins or don't. That's up to you. Maybe your engine supports PBR, maybe it doesn't. Maybe your engine can't handle texture parameters and others do. Maybe your engine handles material animation and others don't. Maybe your engine handles discontinuous animation curves or maybe it doesn't. You aren't get to every engine to support every feature.

The point of this thread is that artists make scenes. Those scenes more often than not use Eulers. If you want to reproduce the artist's work then you need store Eulers in the file.

As for Unity. Yes, Unity uses quats inside. That's issue 2 above. If you make an animation in Unity and key the rotation you'll see it defaults to Eulers. Likely with Euler based animation it animates the Euler values each frame and converts the result to a quat and then runs that through its system.

But if authoring tools all use Eulers (clearly true), and most engines interpolate on Quaternions (also appears to be true), then there's a case to be made that the lossy conversion should happen before writing to glTF,

I disagree and Unity disagrees too. See above. If they agreed they'd never have added the Euler support in. Instead they found it just doesn't work. Artists keep making Euler based animations that don't convert well to quaternions

Unreal as well since they default to Euler animation.

scurest commented 5 years ago

I looked into my question about how much a rotation path depends on the choice of coordinates, eg. Euler or quaternion, different Euler orders. The answer is "a lot".

a The path traced out by interpolate(r1, r2, t) v where r1 and r2 are random rotations, v is a fixed unit vector, and 0≤t≤1 is the time. Different paths come from different choices of coordinates and interpolation. The left-most path uses Euler XYZ, lerping each angle independently; the middle uses quaternions, using slerp; the right-most path uses Euler YZX, again lerping angles.

Euler angles can go on cooler paths too. This shows that augmenting quaternions with "number of spins" is insufficient.

b Example of the Euler YZX path doubling back. Appears to be possible only when the difference between the same angle in r1 and r2 is >90 degrees.

So for accuracy glTF would seem to need to follow Unity here and support all possible Euler orders.

To support a maximum variety of imported files, and for maximum fidelity, Unity supports all normal (non-repeating) euler rotation orders, and curves will be imported in their original rotation orders.

khalv commented 5 years ago

Just randomly stumbled across this issue and reacted to the aggressive tone, as well as the complete misrepresentation of facts. Euler rotations have not been a thing in modeling formats since the 90's due to their numerous limitations. For one, how do you effectively interpolate between two Euler rotations? The answer is - you don't. Euler rotations are also ambiguous since the order of rotation matters. Spherical interpolation for animations are in fact the primary reason as to why Quaternions are favored today, making this statement unintentionally hilarious:

There are appropriate places to use Quaterions but animation data is arguably not one of them.

ALL the programs listed in the example use Quaternions internally, for good reason. The are more compact to store and generally involve less calculations than matrices. In fact, in most modeling programs (including 3ds Max and Blender) you will find that despite the option of showing rotations in Euler format, rotating in increments larger than 180 degrees will still result in a rotation along the shortest path, and you still have to place extra frames every 90 degrees. Unity derives all Euler representations from Quaternions and doesn't even store them.

If programs would need an extra conversion step to make sense of imported data - as would be the case with Euler representations - then that is an issue, especially for performance-critical applications like games. This is why all modern programs use Euler rotations only for the UI, and Quaternions for internal representation.

christophercrouzet commented 5 years ago

In the API of most 3D animation packages, quaternions and Euler angles are not the only representations provided—they usually also come with axis-angle and matrix3 representations. Each have their use in different contexts.

Turns out that these representations can be used to store 2 types of data: orientations and rotations. Orientations describe where something points at, and also include information on the roll angle along the orientation vector. Rotations describe the transformation required to transform an orientation to another.

Quaternions, axis-angle, and matrix3 representations all do a great job at storing orientations but are not suited for rotations since they can not count spins. Euler angles is the only representation that can perfectly store any orientation and any rotation. That is, Euler angles is the only lossless format available if you care both about orientation and rotations.

Conversions between rotation representations are trivial, and are commonly done at run-time depending on the needs (for example, if you need a different interpolation scheme).

As a conclusion, I believe that what matters the most in an exchange format like glTF is to allow users to preserve all the data that they're trying to store, if they want to, instead of having a lossy compression model like JPEG does with images. That is why Euler angles should either be the default, or should at least be available as an option.

ALL the programs listed in the example use Quaternions internally, for good reason.

I bet they don't. Because if they did, they'd be unusable.

khalv commented 5 years ago

I bet they don't. Because if they did, they'd be unusable.

Well, they do, so obviously you're missing something here. Unity states it explicitly in their manual: https://docs.unity3d.com/ScriptReference/Transform-rotation.html

In Blender and 3DS Max, you can try it experimentally by setting rotation mode to Euler and try animating a rotation of 270 or 720 degrees. You will notice that it results in a rotation of 90 and 0 degrees respectively. The "Euler" option is for display only - any values you input are converted to quaternions.

Let me quote the glTF home page:

glTF minimizes both the size of 3D assets, and the runtime processing needed to unpack and use those assets. glTF defines an extensible, common publishing format for 3D content tools and services that streamlines authoring workflows and enables interoperable use of content across the industry.

So to summarize - among the design goals are to deliver low size, low runtime processing cost, and high interpoerability. Quaternions deliver all those things, while Euler rotations do not. Euler angles are not engine-agnostic, since they mean different things for different coordinate systems. Converting between Euler and quaternions require multiple trigonometric operations, and computing this on the fly is not ideal for real-time applications and would completely counteract the performance benefits of using Quaternions. There's also the fact that athough Euler angles are less lossy when it comes to animating >180 degree rotations, they are much less pleasant work with when it comes to actually animating a rotation along multiple axes. Imagine if you just want an object to orient from one direction to another - with Euler angles, the way you rotated it to get to the target pose will matter, even when you don't want it to. If you were to actually use the euler values to interpolate between two (non axis-aligned) rotations, you'd also find that the results are not what you'd expect, and you'd struggle to animate something along a smooth arc. You can refer to it as "cooler paths" like scurest did, but in reality it is not a desired effect. This video illustrates the problem:

https://www.youtube.com/watch?v=QxIdIZ0eKCE

So, if we can't use the Euler values in a meaningful way, then what is the benefit of storing this extra information? Imagine if you want to use glTF files in a program or game which uses quaternion SLERP interpolation for animated rotations (as is practically always the case) then straight up converting the Euler frames to quaternions would produce broken animations if the animator has assumed that they could animate more than 180 degrees in a single frame. You'd have to resample the animation, which makes writing support for the format a hassle.

christophercrouzet commented 5 years ago

You're right—I did read from the previous comments that Unity uses quaternions internally, which is a choice that I find questionable coming from a VFX/animation movies background. But I bet it should pretty much be the exception amongst serious 3D animation packages.

The softwares that I am experienced with are Maya and Softimage. For the uses that these softwares cover, compromising rotation data by irreversibly compressing it into quaternions would not be acceptable and would be a showstopper for the kind of work that I'm doing (character rigging).

I just tried Blender 2.8 and did not see the behaviour that you described. I can go way beyond 360 and it does keep the value, as expected?

At the end of the day, both quaternions and Euler angles are viable solutions for different contexts. If quaternions work for you, then great, enjoy its compression benefits! But it might not be the case for everyone. Which is why it seems fair to let the users decide what is best for them rather than forcefully choosing it for them?

scurest commented 5 years ago

Unity states it explicitly in their manual

Reading https://docs.unity3d.com/Manual/AnimationEulerCurveImport.html

Unity supports such a wide variety of imported files and attempts to keep the imported curves as close to the original as possible. In order to achieve this, Unity supports all normal (non-repeating) Euler rotation orders, and imports curves in their original rotation orders.

Under the hood, Unity stores these curves in Euler representation even at run-time. However, Unity has to convert rotation values to Quaternions eventually, since the engine only works with Quaternions.

So the curves can be stored as Eulers for interpolation. After they are evaluated, they get converted to quaternion. But you need the Eulers for accurate curve evaluation.

In Blender and 3DS Max, you can try it experimentally by setting rotation mode to Euler and try animating a rotation of 270 or 720 degrees

In Blender, this is not correct. It goes the full 450 degrees.

So, if we can't use the Euler values in a meaningful way, then what is the benefit of storing this extra information?

The tradeoff here would be that you get accurate representation of animations that use Eulers. In exchange, all implementations have the added complexity of handling all six Euler angle orders (where handle means handle to the extent that they evaluate animation curves in Eulers; they can convert to an internal format afterwards like Unity).

The alternative (ie. what you need to do now) is that producers may need to convert Euler curves to quaternion, which involves aggressively subsampling the curve until all its segments are small enough that the error between interpolating them with Euler and with quaternions are below some threshold.

ssylvan commented 5 years ago

. Euler angles is the only representation that can perfectly store any orientation and any rotation. That is, Euler angles is the only lossless format available if you care both about orientation and rotations.

That is wrong. Both quaternions and euler angles represent orientation only. Euler angles have no special powers here. It's true that when you interpolate between two quaternions and two sets of euler angles you get very different paths. And they will also depend on how you interpolate (e.g. slerp, nlerp, exponential map.. and maybe you're using a polynomial fit, or smothlerp, etc. and for Euler it depends on which of the six different Euler representations were used as well, etc. etc). There are a million possible permutations here - which one is considered "lossless" depends on which particular quirks the authoring program showed the artist and they "signed off on" by hitting "Export".

So IMO you have two options:

  1. Support every possible interpolation format under the sun via multiple different orientation representations (6x euler, quaternions, matrices, rodrigues, etc.) as well as multiple different interpolation modes (polynomial fits, linear, cubic, exponential map of the same, etc. etc.) and basically drive anyone trying to consume this format crazy.
  2. Make the exporter sample whatever crazy format/interpolation mode their particular program happens to be using often enough that the animation looks good in one format and interpolation mode. I.e. over-sample the animation until the simple linear interpolation of quats looks close enough to what the artist signed off on. (maybe in the future add compression to this to reduce redundancy)

Having lengthy arguments about what quirks a particular program uses (e.g. Maya, Unity etc.) and lobbying that this is the one true "correct" way to do things, seems like an especially bad direction for an interchange format.

IMO 2 is the way to go for something like glTF. And if you can only have one, quaternions with nlerp or slerp are probably the least surprising one (for one thing, they give you the shortest path interpolation, while Euler angles will give you much wilder paths when interpolating).

christophercrouzet commented 5 years ago

That is wrong. Both quaternions and euler angles represent orientation only. Euler angles have no special powers here. It's true that when you interpolate between two quaternions and two sets of euler angles you get very different paths. [...]

I guess that I wasn't explicit enough here. I agree with you but, when I was referring to rotations, I meant simply going from rotation A to rotation B in a mathematical sense, without accounting for animation/interpolation. As you said it, interpolation will change depending on the representation chosen.

I might be missing something here but the way I see it is that interpolation is something not part of the data to be exchanged, it's something that the applications/engines are responsible of. Unless animation curves are also part of the exchange format.

khalv commented 5 years ago

I agree with ssylvan here, except for the fact that there is a point in supporting different interpolation modes. Resampling a curve to the point where it can be linearly interpolated becomes an issue if you want to use glTF to transfer files between modeling programs - the resampled animation might not be entirely straightforward to work with. You also get much more dense data than is possible with splines.

In Blender, this is not correct. It goes the full 450 degrees.

You are in fact right about that. I tested it in 2.79 before i posted, but it appears that the rotation operator automatically performs modulo 360 on all angles you input. You can apparently input larger angles directly in the viewport tab, though.

I might be missing something here but the way I see it is that interpolation is something not part of the data to be exchanged, it's something that the applications/engines are responsible of. Unless animation curves are also part of the exchange format.

The point is that the exported animation should look the same way in whatever program uses them as they did in the program which produced them. Most formats (including glTF) stores the interpolation type as part of the animation data. The rationale behind this is that you might want different interpolation types (if any) for different curves.

emackey commented 5 years ago

For the glTF 2.0 format, it is expected that all exporters should have the ability to sample (or bake, or otherwise convert) whatever form the animations were authored with, into quaternions. This is not meant to indicate that quaternions are "best" or any judgement of quality, it's merely a target format.

It's worth understanding that glTF is intended to be a transmission and delivery format, not an interchange format. Interchange formats try to support all different types of things, in order to preserve the author's original intent across DCC (Digital Content Creation) tools, and as a result the readers of those formats must be prepared to accept a wide array of different types of data from within the format.

glTF takes the opposite approach, it selects a single target (like quaternions, occlusion/rough/metal RGB channel mapping, Y-up axis, right-handed tangent space for normal maps, etc). Sometimes these choices appear arbitrary or opinionated (and maybe they are). But glTF readers only need to understand the one standard target that was selected by glTF. The burden is all on the exporters, to organize their data to fit whatever was selected as standard, by whatever means, before going into glTF format. Then, the readers don't have to process things this way and that, they only have to accept the one standard form for each thing. Ensuring this conformity has offered the glTF format a very wide reach across a great variety of platforms.

Bringing the conversation back to animation: Yes, Euler angles are quite useful for animators. I'm not a real animator myself but I routinely need to make a wheel spin or radar dish go around, and I'm grateful that 0 and 360 degrees are not the same thing in Blender's animation curves. But to get into glTF, that means not only converting to quaternions, it means including extra keyframes to make the quaternions actually work for angles that far apart. Ideally the exporter would be smart enough to add those automatically (it's not yet, PRs welcome of course).

Again, none of this is meant to claim something is optimal, best, or ideal. But hopefully understanding some of the design goals of glTF is illuminating for how we came to this point.

ssylvan commented 5 years ago

I guess that I wasn't explicit enough here. I agree with you but, when I was referring to rotations, I meant simply going from rotation A to rotation B in a mathematical sense, without accounting for animation/interpolation.

You can use a quaternion to go from rotation A to B as well. Euler angles are not special here - both just represent orientations. They have some different quirks - in particular quaternions have two representations for each orientation, while euler angles have an infinite number of them. This quirk combined with some interpolation modes may give you the ability to e.g. specify 5000 degrees of rotation with a single key frame, but that's not a property of euler angles, that's a particular quirk of the way a particular program chooses to interpolate them. I don't think a format like glTF should be in the business of emulating an unbounded number of authoring programs' quirks.

christophercrouzet commented 5 years ago

It's worth understanding that glTF is intended to be a transmission and delivery format, not an interchange format. Interchange formats try to support all different types of things, in order to preserve the author's original intent across DCC (Digital Content Creation) tools [...]

I guess this clears things up 😛

You can use a quaternion to go from rotation A to B as well. Euler angles are not special here - both just represent orientations. They have some different quirks - in particular quaternions have two representations for each orientation, while euler angles have an infinite number of them.

Let's say that I want to export 2 boxes. There's one box at the bottom, which is not animated, and one a bit higher up which has an animation that goes from 0 to 720 degrees on the Y (up) axis. In the application/engine that I'll import this animation into, I will create a rig that will generate a variable number of little cubes that will be dynamically constrained/interpolated between the 2 animated boxes, to form a nice spiralling motion. A little bit like in this image. Question: how would I do that with quaternions?

Now, I understand if you do not wish to cover this kind of scenarios because deemed an edge case, or whatnot. I just wanted to highlight that, from a rigging point of view (and probably others!), there are practical scenarios where Euler angles are the only choice, and where no amount of keyframe on quaternions will change that.

ssylvan commented 5 years ago

You do it with quaternions just like you do it with euler angles (by adding key frames with intermediate orientations where it makes sense in order to get the animation you want). There's nothing special about Euler angles - they just represent orientations. It could be that a particular program uses euler angles in a way that isn't supported by the way they use quaternions, or that in one particular instance the crazy roundabout way that euler angles interpolate (non shortest path) happens to be the effect you're going for, but that's an incidental quirk of the interpolation mode, not a fundamental property of quaternions vs euler angles. At the end of the day they both just represent orientations. What you do with them is a separate issue, and whatever animation you get from euler angles can be converted to quaternions by sampling at a high enough rate (and vice versa).

christophercrouzet commented 5 years ago

But... I don't want orientations. I want to compute the rotation, that is the difference between the orientation of the 2 boxes, then linearly interpolate that rotation to each little cube inbetween.

Alright, less words, more pictures :stuck_out_tongue:

I built a Houdini scene as an exemple—Houdini is a powerful 3D software for high-end VFX boasting a procedural workflow. You can check the result on this Vimeo link (password: gltf).

In both cases, the top box has an animation where its Euler rotation on the Y axis goes from from 0 to 720 degrees.

On the left side, the inbetween cube has its orientation directly computed from the interpolation of the Euler angles of the bottom and top boxes.

On the right side, the bottom and top boxes have their Euler angles converted to quaternions, then the orientation of the inbetween cube is interpolated from these.

Even though it is expected that Euler angles and quaternions have differing interpolations, this shouldn't be the case here for such a simple 1-axis rotation. And yet.

But let's forget about that and let's focus instead on the last keyframe where the Y-rotation of the upper box is set to 720 degrees. If we have 0 for the Y-position of the bottom box, and 1 for the Y-position of the upper box then, with the inbetween cube located at 0.3, I'd expect the inbetween cube to have an angle value of (720 - 0) * 0.3 = 216. Which, only the Euler angles can represent correctly as seen in the screenshot below.

The scene is downloadable here. Houdini has a free 'Apprentice' license and is downloadable here.

Conclusion: if I used glTF to import my animation of the bottom and top boxes, I'd only be able to build a broken rig due to my orientations having been converted to quaternions.

PS: I really should have spent more time making a less confusing scene with either colors or other things than boxes, it would have been easier to describe, apologies.

niklas-ourmachinery commented 5 years ago

As @ssylvan says there are only two options that make sense:

  1. Store an exact representation of the original animation curves.
  2. Store an approximate representation of the original animation curves.

I believe (1) is going down a rabbit hole that is too deep to climb out of, because every program can have its own representation of animation curves (which axes to use for Euler angles, interpolation model, world space or object space animation, etc). To be exact, GLTF would have to support all of them (or it would just be exact for one particular program). In addition, there can be other things besides the curves affecting animations: controllers, look-ats etc. Again, to be exact, GLTF would have to represent all of this, in every possible program. Way too complicated.

For (2) we just want to make sure to pick a format that is as simple as possible. Quaternions win here, since they don't have the axis ordering issues of Euler angles.

It is also important for (2) to define the assumed interpolation. I.e., I think that GLTF should define that nlerp interpolation is used between the animation keys. That way an exporter knows how the sampled animation will be interpreted and can add as many keyframes as required to get below a desired error threshold.

khalv commented 5 years ago

christophercrouzet: It is possible to create an animation curve with quaternions every 90 degrees and then evaluate a position on this curve to get the effect you want. If you need the amount of relative spin between two objects in order to perform some arbitrary logic, then i kind of feel like it falls beyond the scope of what you can expect from a general-purpose modeling format.

Quaternions are used by most game animation systems, so it makes sense to store the information in that format since it won't need to be converted and can be easily loaded on the fly.

ssylvan commented 5 years ago

@khalv is exactly right! If you want to emulate any particular Euler rotation using quaternions, you can create a hierarchy of 3 quaternion rotation nodes under each other to apply the 3 cardinal axis rotations cumulatively (just like an Euler rotation does). E.g. a "yaw" rotation node, with a "pitch" child node and a "roll" child node under that, each represented as a quaternion rotating around the corresponding axis.

However, as someone else pointed out earlier, content creation tools can have some crazy interpolation modes or other things affecting the final rotation (such as constraints) anyway, so in practice you may still have to just sample the animation fairly densely to get a good representation (this applies with either euler angles or quaternions). So in that case you may as well not bother trying to "emulate" the euler angles directly - just convert the orientation at each sample point to a single quaternion.

Note that the opposite is not true. If you're trying to emulate a quaternion rotation with euler angles, the only way to do it is to densely sample the animation and add lots of in-between key frames to avoid the euler version diverging too much from the ground truth animation. There's no way to "emulate" quaternion behavior with euler angles directly.

christophercrouzet commented 5 years ago

I believe (1) is going down a rabbit hole that is too deep to climb out of [...]

I'm not saying that it's the way to go but I don't think that it'd be as complicated to implement as what you describe (@ssylvan described something else, I believe). There are exchange formats such as COLLADA (to stay within the Khronos domain) that can describe animation curves with a common denominator feature set, and that is plenty enough to cover most needs (other needs can still be converted to what's supported by these exchange formats). Also, look-ats and others fall in the constraints category, these are usually too specific to each package to be contained within such an exchange format, hence these constraints are usually baked into animation curves (by sampling their value at each frame) before exporting.

[...] then i kind of feel like it falls beyond the scope of what you can expect from a general-purpose modeling format.

As I said earlier, that's fine! If Khronos does not want to support usage from outside the gaming industry (e.g. the film industry), then no worries, I understand. I only wanted to share my point of view on the possible shortcomings that I would encounter if I were to use it myself, to provide some more context to this discussion and help you guys to take an informed decision.

christophercrouzet commented 5 years ago

[...] you can create a hierarchy of 3 quaternion rotation nodes under each other to apply the 3 cardinal axis rotations cumulatively (just like an Euler rotation does) [...]

Can't tell if you're kidding or not?

[...] just convert the orientation at each sample point to a single quaternion.

Sure! But then it might not contribute to keeping small file sizes though.

Also, I'm wondering how a render with motion blur enabled would look like if it reads Euler angles that jump from 360 to 0 degrees? :thinking: :stuck_out_tongue:

ssylvan commented 5 years ago

Can't tell if you're kidding or not?

No? Euler angles are just three rotations composed, and you can easily (during export) create three quaternion rotations composed (via hierarchy). So if you really wanted to preserve the euler angle behavior it's not that hard to do with quaternions (in the exporter). But as I said later, I suspect there are a myriad of other quirky behavior in the content creation tool that can't be emulated, so you'll probably need to super sample the animation anyway.

christophercrouzet commented 5 years ago

I never tried but I believe you that it works. It just didn't sound like a workaround that, as a user looking for user-friendly tools, I would be willing to go for :stuck_out_tongue:

niklas-ourmachinery commented 5 years ago

Also, look-ats and others fall in the constraints category, these are usually too specific to each package to be contained within such an exchange format, hence these constraints are usually baked into animation curves (by sampling their value at each frame) before exporting.

Yes, so then you are back into (2) where you only get approximate curve representations based on sampling/baking. To me it doesn't seem worth it to add the complexity of (1) when you still only will get exact curve representations in some special cases (the right interpolation, no constraints, etc).

ssylvan commented 5 years ago

It just didn't sound like a workaround that, as a user looking for user-friendly tools, I would be willing to go for

You probably should not be writing glTF by hand. The exporter would do this (but again, there are so many other reasons why you'd need to resample the curve anway, constraints etc., that it's probably not worth the hassle for an exporter to support).

vpenades commented 5 years ago

In my humble opinion, I think we should stick to use quaternions and nothing else, as @Ziriax said, glTF is intended to show the final result of the animation, not all the processes used to generate the final result.

It is true that baking animations to quaternions has the limitation of +180 degrees rotation, but it can be easily overcome by the exporter by adding additional keys.

Incorporating authoring animation concepts into glTF can easily become a never ending nightmare; We begin by supporting Euler angles, which leads to support:

There's more issue to take into account:

To be fair, it could be possible to add some basic support for quaternions rotating more than 180 degrees.... maybe adding a fifth value indicating how many 180 degree turns to take, so a runtime could insert additional quaternions in between keys.

garyo commented 5 years ago

I'd just like to add one thing to this already-too-long discussion.

I'm actually a big fan of storing quaternions in glTF as it's done today rather than Euler angles, mostly because natural slerp looks pretty and it keeps the implementation (readers and writers) simple. My comment is about the bigger picture: it's often said that "glTF is intended to be a transmission and delivery format, not an interchange format" and that's clearly one big reason for its rapid adoption and success -- keeping things simple. But it just so happens that glTF is actually, for some uses, an excellent interchange format, much better than the others out there. I'm writing an application that interfaces bidirectionally between three different 3d systems (at least) and glTF is serving really well as the lingua franca between them (esp. now it has lights). So to the glTF authors: well done, and although you risk becoming a victim of your own success, I hope glTF does continue to find that tricky balance between being a complete scene description and yet keeping the core simple and clean.