Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the option to simulate Godot 3 blending where the result depends on the connection order of AnimationNodes in Godot 4.x #9215

Open
jcarlosrc opened this issue Mar 2, 2024 · 35 comments

Comments

@jcarlosrc
Copy link

Describe the project you are working on

A FPS shooter game with visual hands and many weapons.

Describe the problem or limitation you are having in your project

The new Godot 4 animation system is designed to be deterministic based on a RESET pose. This can be technically better in many aspects. However, the way it calculates the minimum rotation between animations is not what we can expect in many situations. It prioritizes the path towards the RESET pose. This causes different issues with imported animations, specially animations that are far from the RESET pose. The old Godot 3 behavior was far more stable and predictable than the current state of the engine in many cases. I know that it is technically possible to fix this problems but this requires a kind of unnatural amount of work: changing the rest pose based on the player state, avoiding making animations far from the RESET pose and so on, all manual work just for animations to work. These could be avoided in many cases just by re-enabling the old Godot 3 behavior.

godot3_behavior.mp4
godot4_behavior.mp4

Describe the feature / enhancement and how it helps to overcome the problem or limitation

The newest releases of Godot 4 (4.2.1, 4.3 dev) have the option to disable the deterministic behavior. This was supposed to get back the old Godot 3 behavior but it has not. It would be good to have the option to enable the old Godot 3 animation blending behavior in this option or in another one.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

When selecting an Animation Tree, the option to disable the deterministic behavior should get back the Godot 3 blending algorithm.

If this enhancement will not be used often, can it be worked around with a few lines of script?

It is a core feature than would help move Godot 3 projects to the Godot 4.

Is there a reason why this should be core and not an add-on in the asset library?

It is a core feature.

@TokageItLab
Copy link
Member

TokageItLab commented Mar 2, 2024

As mentioned in godotengine/godot#81021 (comment), this is a problem that can be solved with a few lines of code, as there is already a workaround to rewrite rest. Also without writing code, you should be able to change the rest using the StateMachine By setting the desired rest as a keyframe (Case by case, UpdateMode.Discrete and CallbackModeDiscrete.Dominant, which will be implemented in Godot 4.3 may need to be set).

So as mentioned in there, this should not be implemented because of the inevitable problem of unavailability.

The newest releases of Godot 4 (4.2.1, 4.3 dev) have the option to disable the deterministic behavior. This was supposed to get back the old Godot 3 behavior but it has not.

It is an option like whether or not to use the default value when the blend amount is 0. It is more like an option of Normalized or not, and is equivalent to Unity's "write defaults" option.

The blending in Godot 3 was not only non-deterministic, but completely incorrect and broken in its calculations as you can see it in godotengine/godot#34134 (comment) clearly.

@huwpascoe
Copy link

So reading those other threads it seems Blender, Maya, Unity and Unreal able to do this, yet Godot needs a workaround with code or whatever.

I have no personal stake in this issue, but don't you think it's odd that all these other engines and tools just work? What makes Godot special here? Is it a performance thing? If that's the case shouldn't the animation importer process the frames to conform instead of the user?

It's hard to believe this can't be implemented. What's the problem?

@TokageItLab
Copy link
Member

TokageItLab commented Mar 2, 2024

Godot has rest as separate data from the animation list for the rotation of the reference. But some software may use one of the animation in lists that actually used by the blending at the time as the reference rotation.

In other words, other software is proprietary and user undetectable as to what the base rotation is, whereas Godot allows the user to explicitly specify the base rotation as rest.

Or, as I have mentioned many times, they may have the following problem. This works for simple blends, but breaks down for complex blends:

Unpredictable rotations: It does not make sense to get the shortest path from the prev frame. Even if it is the shortest path when comparing blended animations' current frame, it may will be the not shortest path by progression of the animation during blending. At the moment it is no longer the shortest path, the blending will cause an unintended flip.


So reading those other threads it seems Blender, Maya, Unity and Unreal able to do this, yet Godot needs a workaround with code or whatever.

Also, the problem in godotengine/godot#88805 is a bit different from the problem in godotengine/godot#81021. Unity and Unreal are forced to rewrite the base rest by retargeting, so the problem godotengine/godot#81021 did not occur.

Since problem godotengine/godot#81021 did not use Godot retargeting, if it use Godot retargeting and fixes the rest, there should be no problem there, just like with any other game engine.

We have not yet verified whether the problem godotengine/godot#88805 occurs in game engines other than Godot, but it may occur in other game engines with the same problem as Godot.

If it doesn't occur, then the problem may be as described above, or the internal rest may be different. At least for Unity Mecanim, I remember that they were based on something like bike pose which is more appropriate as a median range of muscle motion than T-pose, but I don't know if setting it as rest would solve all the problems. If that is preferred, I assume we can send a proposal to add an option to the importer to make the rest a bike-pose as the superseding this proposal.

So all we should be able to do is to make the rests easier to set up or come up with better rests, and the Godot3 broken blending godotengine/godot#34134 (comment) will never be reimplemented, because it is incorrect mathematically and is not maintainable since it involves rewriting the entire blending algorithm of Godot4 blending. If do it, many if statements would be needed at every calculation point or all code for blending would need to be duplicated, which should not be done to bring back the problematic blending.

image

https://docs.unity3d.com/Manual/MuscleDefinitions.html

@TokageItLab TokageItLab changed the title Add the option to get back Godot 3 animation blending algorithm for Animation Tree in Godot 4.x Add the option to simulate Godot3 blending where the result depends on the AnomationNode order connection in Godot 4.x Mar 2, 2024
@TokageItLab TokageItLab changed the title Add the option to simulate Godot3 blending where the result depends on the AnomationNode order connection in Godot 4.x Add the option to simulate Godot 3 blending where the result depends on the AnomationNode order connection in Godot 4.x Mar 2, 2024
@TokageItLab TokageItLab changed the title Add the option to simulate Godot 3 blending where the result depends on the AnomationNode order connection in Godot 4.x Add the option to simulate Godot 3 blending where the result depends on the connection order of AnomationNodes in Godot 4.x Mar 2, 2024
@TokageItLab TokageItLab changed the title Add the option to simulate Godot 3 blending where the result depends on the connection order of AnomationNodes in Godot 4.x Add the option to simulate Godot 3 blending where the result depends on the connection order of AnimationNodes in Godot 4.x Mar 2, 2024
@jcarlosrc
Copy link
Author

jcarlosrc commented Mar 2, 2024

As we can see in the official documentation
https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html#for-better-blending
if we want to blend two animations, the min rotation path from Rest pose is prioritized over the minimum path between the two animations to blend. I do not know if this absolutely necessary. In the example provided in the official link, the left arm rotates from "pointing front" to "pointing up". However it does it passing through the Rest pose (pointing left). Why? None of the two animations pointed towards left. We would expect that it would rotate from the initial pose to the next directly. This is, passing from pointing front to pointing up directly.

issue

I don't know if that way of blending (always prioritizing the rotation from the Rest instead of just the rotation between the two desired animations), is really absolutely necessary. I clearly generates unwanted visual behaviors. Is it impossible to blend the two animations directly? There is a proposed workaround: setting a non-rest animation as a Rest pose every time we want to have a direct path between two animations to minimize the effect. This is clearly a manual work and as I can see there is not enough documentation about it. It also assumes that the programmer is using a state machine and forces to work manually in the blending process. It really shouldn't be necessary to have workarounds to blend just two (visually) simple animations. What if we want to blend three different animations? Do we need to create a Rest pose that stays in the middle of the three just for the blending to work visually ok?
Anyway, maybe there can be done something on top of the current state of the blending algorithm to avoid the need of such workarounds, at least in cases that should be simple enough to "just work".

@TokageItLab
Copy link
Member

TokageItLab commented Mar 3, 2024

As we can see in the official documentation
https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html#for-better-blending
if we want to blend two animations, the min rotation path from Rest pose is prioritized over the minimum path between the two animations to blend. I do not know if this absolutely necessary. In the example provided in the official link, the left arm rotates from "pointing front" to "pointing up". However it does it passing through the Rest pose (pointing left). Why?

If you use shortest rotation path from current frame, you will get rotations that are not possible in human articulation; In the document case, this means that the legs interpolates through the body. Probably other game engines with Humanoid will use similar interpolation with Godot, although may have a different rest like Unity's bike pose.

In Godot, if you want such interpolation (meaningful mathematically but potentially broken ergonomically) you can solve it by explicitly changing rest, but I don't know how other game engines solve this or not possible.

I don't know if that way of blending (always prioritizing the rotation from the Rest instead of just the rotation between the two desired animations), is really absolutely necessary. I clearly generates unwanted visual behaviors. Is it impossible to blend the two animations directly? There is a proposed workaround: setting a non-rest animation as a Rest pose every time we want to have a direct path between two animations to minimize the effect.

The current blending produces interpolation that is visually preferable to displaying a model with broken joints. We can discuss ways to set the rest (and RESET for 2D), but it must be optional and should never be the default.

At least for the problems you encountered with in godotengine/godot#88805, simply setting the bike pose to rest might improve the problem, then I wonder if more workarounds are needed.

@jcarlosrc
Copy link
Author

The current blending produces interpolation that is visually preferable to displaying a model with broken joints. We can discuss ways to set the rest (and RESET for 2D), but it must be optional and should never be the default.

Here there is a pick from the official Godot docs about animation tree:
animtree15

Notice that: The run animation points the right arm front. Then it is blended with the idle animation, which also points the right arm front. However, the right arm points to the right in the process. I cannot see how this would be a natural movement. Who makes such strange movement between two poses that are very similar? This blending algorithm breaks a basic principle: if the two animations are similar, the in-between frames should remain similar. If the animator makes two animations similar, they are intended to create seamless transitions between them.

At least for the problems you encountered with in godotengine/godot#88805, simply setting the bike pose to rest might improve the problem, then I wonder if more workarounds are needed.

About the much talked about workaround: I understand that the RESET pose needs to be changed in the runtime every time we want to "fix" the blending process. This would require taking an specific frame from the desired animation from the AnimationPlayer and set it as reset pose for every bone. I would appreciate you shared your code because there is not much documentation about it. Thanks.

@huwpascoe
Copy link

The run animation points the right arm front. Then it is blended with the idle animation, which also points the right arm front

I'm more concerned with the knees. What's with those weird triangles clipping through?

@Calinou
Copy link
Member

Calinou commented Mar 9, 2024

I'm more concerned with the knees. What's with those weird triangles clipping through?

This is likely an issue with the source mesh or engine rendering that was fixed a long time ago. The GIF was recorded on a development build of Godot 3.1 in 2018 after all 🙂

@TokageItLab
Copy link
Member

TokageItLab commented Mar 12, 2024

The some robot gifs are very old as mentioned above, so the blending result has been changed and should be improved in Godot 4 already.

About the much talked about workaround: I understand that the RESET pose needs to be changed in the runtime every time we want to "fix" the blending process. This would require taking an specific frame from the desired animation from the AnimationPlayer and set it as reset pose for every bone. I would appreciate you shared your code because there is not much documentation about it.

This method should work by firing just in one frame at the time of an important State change. Note that it is necessary to explicitly clear the cache, since AnimationMixer does not detect changes to the skeleton since the rest is cached.

Skeleton3D.gd

@onready var tree: AnimationTree = get_node("../../../AnimationTree")

func apply_rest() -> void:
	for i in range(get_bone_count()):
		set_bone_rest(i, get_bone_pose(i))
	tree.clear_caches()

If there is a crossfade at State change and the current pose is not desirable as a rest, you will need to hold an arbitrary rest somewhere. If you do not like to record data statically, you can create the method (make add-on would be good) to extract the poses from Animation.

Skeleton3D.gd

const RESTS_HOLD: Array[Transform3D] = []
@onready var tree: AnimationTree = get_node("../../../AnimationTree")

func apply_rest() -> void:
	for i in range(get_bone_count()):
		set_bone_rest(i, RESTS_HOLD[i])
	tree.clear_caches()

@huwpascoe
Copy link

Skeleton3D.gd

Can this be done during the import process? If this is how the animation system works may as well go all in and make it a feature.

Not sure how to present this to the animator though. A new state node? Some kinda drop-down "use this animation as reference pose"?

@TokageItLab
Copy link
Member

TokageItLab commented Mar 15, 2024

Can this be done during the import process?

If you want to make a specific rest like bike pose, the default, we can already do this by creating a SkeletonProfile and use Fix Silhouette option.

There may be cases where you want to set a non T-pose neither bike-pose rest from an arbitrary animation in the importer, but as explained above, that can also cause it to provide a broken blend as I mentioned in first of #9215 (comment).

The rest should be basically static and midway between the range of motion of the muscles, and this is true in Unity and UE as well. It is not recommended to use it all the time, unless you want to change rest only during certain transitions.

The issue with godotengine/godot#88805 is definitely a niche case, since the blending in DCC software like blender (blending considering only simple state transitions, unsafe) and the blending in game engines like Godot, Unity, and Unreal (blending considering complex state transitions) should have different blending result.

So, as mentions in #9215 (comment), the only thing that could possibly be added here would be to provide a method to extract rest from the animation 1 to help to handle niche cases easily, like by calling it on the MethodTrack or signal IMO.

Footnotes

  1. However, doing this without sacrificing either performance or architectural design is slightly difficult. If we extract only the rest from the animation, the animation only has bone names without index, so the rest is stored in a dictionary type, which has poor performance when used by skeletons. Ideally, a method like override_skeleton_rest() should be added to the AnimationMixer for performance, but this is bad architectural design since it makes the AnimationMixer and Skeleton3D are tightly coupled. So for now we can conclude that it is not a good idea to add this to the core since we cannot accept to pollute the core for a niche case. That is why I recommended making it an add-on or extracting the rest statically.

@MatthewBlanchard
Copy link

MatthewBlanchard commented Apr 26, 2024

The some robot gifs are very old as mentioned above, so the blending result has been changed and should be improved in Godot 4 already.

About the much talked about workaround: I understand that the RESET pose needs to be changed in the runtime every time we want to "fix" the blending process. This would require taking an specific frame from the desired animation from the AnimationPlayer and set it as reset pose for every bone. I would appreciate you shared your code because there is not much documentation about it.

This method should work by firing just in one frame at the time of an important State change. Note that it is necessary to explicitly clear the cache, since AnimationMixer does not detect changes to the skeleton since the rest is cached.

Skeleton3D.gd

@onready var tree: AnimationTree = get_node("../../../AnimationTree")

func apply_rest() -> void:
	for i in range(get_bone_count()):
		set_bone_rest(i, get_bone_pose(i))
	tree.clear_caches()

If there is a crossfade at State change and the current pose is not desirable as a rest, you will need to hold an arbitrary rest somewhere. If you do not like to record data statically, you can create the method (make add-on would be good) to extract the poses from Animation.

Skeleton3D.gd

const RESTS_HOLD: Array[Transform3D] = []
@onready var tree: AnimationTree = get_node("../../../AnimationTree")

func apply_rest() -> void:
	for i in range(get_bone_count()):
		set_bone_rest(i, RESTS_HOLD[i])
	tree.clear_caches()

This is a really ugly kludge for something you should be able to do in engine with low/no code. Why is it currently impossible to do simple things like:

Blending between orientations of a turret/wheel/etc and getting smooth rotations.
Have characters do 180+ degree flips without root motion.
Action movie akimbo dual wield pistol spin.

This might be 'expected behavior' and fix the breakdown of complicated blends from 3.x but it creates an ergonomic nightmare for what should be trivial animations.

I'm trying to do a matrix dive in my game. I have a dive_left, dive_forward, dive_back, etc. I should be able to interpolate by the shorter path the animation is one bone even. Can I? No it wraps the wrong way around because of rest position.

Watching what animation is playing in code and then manually setting the rest position based on that is JANK and really shouldn't be considered a real solution.

I've read like everyone of these issues about this problem and it's just constant dismissal or recommendations of work around that really shouldn't be necessary for simple cases like this. I'm glad the changes to animations fixed advanced blending but they made a basic one an absolute nightmare.

It might not be mathematically a problem, but it's an ergonomic one. It should be easy to drive say a turret with animation blends to point in a specific direction, or a wheel spinning based on 4 poses. And yes, I've read the replies to all the other issues. You CAN use IK, root motion, or drive the bones directly through code, but you shouldn't have to. It's a game engine and you've gotta get deep into the weeds to make a wheel spin the right way? Give me a break.

Maybe when there's like 10 issues opened about this the takeaway should be this is a bug or at the very least unexpected behavior.

Can you please accept that this is in the eyes of the consumer a bug and something wrong with the animation system when the same examples work in other software? PLEASE just mark one of these issues a bug instead of closing them.

@huwpascoe
Copy link

Maybe when there's like 10 issues opened about this the takeaway should be this is a bug or at the very least unexpected behavior.

Okay. Let's fix this.

Ideally, a method like override_skeleton_rest() should be added to the AnimationMixer for performance, but this is bad architectural design since it makes the AnimationMixer and Skeleton3D are tightly coupled

Skimmed over the source, looks like bones have a property rest: Transform3D that can be animated like any other. So there's no need to couple anything right?

@Kliefer
Copy link

Kliefer commented Apr 27, 2024

I would love it to be fixed! I mean isnt Blender also FOSS so you could copy or rather implement their version to tackle this problem, all 3d animators working with godot are hating it so far becasue the setup takes so long and you need to test longer for werid edge cases.

@Calinou
Copy link
Member

Calinou commented Apr 27, 2024

I mean isnt Blender also FOSS so you could copy or rather implement their version to tackle this problem

We can't copy code from Blender as it's GPL-licensed, while Godot is MIT-licensed.

@Kliefer
Copy link

Kliefer commented Apr 27, 2024

ok I get that godot wants to stay MIT. If you would distribute the resulting work it would be in accordance with GPL but that would mean Godot needs to be GPL.
I just hope there will be a more userfriendly solution Matthew and my team aren't the only ones who are frustrated how the animation system interprets the animation.> > As previously mentioned in #81755 (comment), the shortest path interpolation based on the current animation has the following serious unusable problems:

Unpredictable rotations: It does not make sense to get the shortest path from the prev frame. Even if it is the shortest path when comparing blended animations' current frame, it may will be the not shortest path by progression of the animation during blending. At the moment it is no longer the shortest path, the blending will cause an unintended flip.

Moreover, if we want to do it with Godot, the blending result depends on the iterating order, meaning that the same amount of blending will change depending on the order in which the nodes are connected.
Probably any DCC software, not just Godot, will have to choose between that using current frame interpolation with unusable problem or a deterministic solution based on rest.
Also, if using solution with rest basis, there is a workaround to rewrite rest depending on state as mentioned in #88805 (comment). It is the programmer's role, so there is no need to mind about this on the animator's side.

Man I don't know how you can say this with a straight face. Every other engine in this example just worked when they imported the animation. Your continued closing of these issues and recommendations of jank fixes for BASIC animations is absurd. Can you please just mark ONE of these issues as an actual bug and leave it open?

I'd rather deal with whatever the consequences of the shortest path + advanced blending are then have my animation blends require a hand tailored JANK code fix.

It is like Mattew says it feels weird how it is not really adressed rather just deal with it and ignored

@MatthewBlanchard
Copy link

Maybe when there's like 10 issues opened about this the takeaway should be this is a bug or at the very least unexpected behavior.

Okay. Let's fix this.

Ideally, a method like override_skeleton_rest() should be added to the AnimationMixer for performance, but this is bad architectural design since it makes the AnimationMixer and Skeleton3D are tightly coupled

Skimmed over the source, looks like bones have a property rest: Transform3D that can be animated like any other. So there's no need to couple anything right?

I built the engine yesterday and have been hacking away looking for a fix. I got something that works for me, but is unlikely a general solution on more advanced blends.

2024-04-27.17-33-27.mp4

Here's what I want above which I got by modifying Godot's source code.

2024-04-27.17-23-15.mp4

Notice that it interpolates the long way around?

If nothing else the OPTION to do it the first way is needed.

@TokageItLab
Copy link
Member

@MatthewBlanchard This is similar to the behavior of godot 3 and should probably result in unintended transitions in complex blends. With your algorithm, when blending the results of blending A and B with C, it is rightward. Now when you change the blend result of A and B, what happens if the shortest path is leftward?

@MatthewBlanchard
Copy link

@MatthewBlanchard This is similar to the behavior of godot 3 and should probably result in unintended transitions in complex blends. With your algorithm, when blending the results of blending A and B with C, it is rightward. Now when you change the blend result of A and B, what happens if the shortest path is leftward?

Who cares? I have the other blend method for that. At least give me the option to use this one. It is clearly better in this case and doesn't cause a massive unwanted common sense destroying 180 degree spin.

@TokageItLab
Copy link
Member

@MatthewBlanchard It is not a matter of worrying in advance, but of the potential for broken transitions there because of unintended complications.

@MatthewBlanchard
Copy link

MatthewBlanchard commented Apr 28, 2024

@MatthewBlanchard It is not a matter of worrying in advance, but of the potential for broken transitions there because of unintended complications.

My transitions are already broken by the existing interpolation. I would take complicated blends being broken over this extremely simple relatively common case any day.

What about for other types of skinned meshes that aren't humanoids? Does lerping through a REST state make sense for a valve that can spin in either direction? Does it make sense for a turret that can point anywhere in a hemisphere to lerp through? Not really. It in itself seems like a non-general solution created to fix complicated blends on humanoids.

In my specific usecase all of my limbs are driven by springs and IK. I don't want this behavior -at all-.

@TokageItLab
Copy link
Member

TokageItLab commented Apr 28, 2024

@MatthewBlanchard I understand that the existing blend depends on rests and that there are UX issues that prevent users from easily changing the rests, but there is no bug there.

For the case where the bones are not humanoid, there certainly could be undesirable results, but it is necessary to investigate how other game engines handle this.

The breakdown of complex blends I mentioned is a matter of Quaternion's nature, so it should support euler rotation beyond 360 degrees and have the ability to select euler completion if it is to provide consistent results. It will not be the shortest path, but it will give consistent results. As for the immediate shortest path method, I believe that it can only produce broken results in complex state without rest.

@MatthewBlanchard
Copy link

@MatthewBlanchard I understand that the existing blend depends on rests and that there are UX issues that prevent users from easily changing the rests, but there is no bug there.

For the case where the bones are not humanoid, there certainly could be undesirable results, but it is necessary to investigate how other game engines handle this.

The breakdown of complex blends I mentioned is a matter of Quaternion's nature, so it should support euler rotation beyond 360 degrees and have the ability to select euler completion if it is to provide consistent results.

Could you please provide me with an example of these complex blend states this breaks? I'm going to look for a more general solution to this problem or at the very least a more ergonomic one than the ones you've suggested.

@TokageItLab
Copy link
Member

As mentioned above:

when blending the results of blending A and B with C, it is rightward. Now when you change the blend result of A and B, what happens if the shortest path is leftward?

Also, Godot 3's blending had a problem where the result changed depending on whether the nodes were connected top or bottom input port, so that needs to be checked as well.

@MatthewBlanchard
Copy link

As mentioned above:

when blending the results of blending A and B with C, it is rightward. Now when you change the blend result of A and B, what happens if the shortest path is leftward?

Also, Godot 3's blending had a problem where the result changed depending on whether the nodes were connected top or bottom input port, so that needs to be checked as well.

Can you point me to an actual reproducible example I can test my build against as I work? Or at the very least an issue containing one.

@TokageItLab
Copy link
Member

TokageItLab commented Apr 28, 2024

@MatthewBlanchard

blending.zip

This project is guaranteed the following behavior depending on the value of rest (RESET animation key in Node3D::Transform3D) in current Godot 4's algorithm.

left.tscn: counterclockwise rotation
right.tscn: clockwise rotation
animated_left.tscn: counterclockwise rotation (with animation)
animated_right.tscn: clockwise rotation (with animation)

If it does not depend on rest (as with your algorithm), find out what transitions occur if you change the value of the Blend 2 2 node when the value of the Blend 2 node is around the middle.

Also, in order to avoid regression to Godot 3, please check that for the same weighting (e.g., all are 0.5), the result of swapping the top and bottom of the Blend 2 input is equal.

Edited:
Updated project to add more some cases with animation.

@TokageItLab
Copy link
Member

TokageItLab commented Apr 28, 2024

I upload a video to easily understand the problem of on the fly shortest path without reference to a specific Quaternion(Basis) as rest.

shortest_rotation_problem.mp4

This flip can occur even in less complex cases.

It should be noted that a flip is a glitch in anyone's eyes due to its abrupt change in visual appearance, and for whatever reason there is it gives the first impression of being a bug to many users.

If the flip does not occur, then it is not the shortest path. However, it is visually preferable than the flip occurring. Then, it should not occur randomly, it should have some consistency, but rest is a guiding axis that ensures that consistency. Behavior with respect to a particular Quaternion(Basis) is like a pole Axis in IK or a guide rail in spline wrap, it is not strange that it exists.

#9215 (comment)

I don't think AnimationTree should be used in the first place in cases where such a rotation is desired. It is doing attitude control, not blending, so it sounds like you want something that is clearly out of scope of "blending".

In short, we should not support it for the special cases on the assumption that flips will happen without proper reason. Then, the right way to go is to find a way to make it easier to set up rest/RESET.

The possibly valid justification here is compatibility with other game engines.

If other game engines support it on the assumption that the visual will break, then there is a chance that it could be implemented as an option with a name like “Unsteady Rotation”, where it is obvious that there will be a weird look to it.

@MatthewBlanchard
Copy link

MatthewBlanchard commented Apr 28, 2024

I upload a video to easily understand the problem of on the fly shortest path without reference to a specific Quaternion(Basis) as rest.
shortest_rotation_problem.mp4

This flip can occur even in less complex cases.

It should be noted that a flip is a glitch in anyone's eyes due to its abrupt change in visual appearance, and for whatever reason there is it gives the first impression of being a bug to many users.

If the flip does not occur, then it is not the shortest path. However, it is visually preferable than the flip occurring. Then, it should not occur randomly, it should have some consistency, but rest is a guiding axis that ensures that consistency. Behavior with respect to a particular Quaternion(Basis) is like a pole Axis in IK or a guide rail in spline wrap, it is not strange that it exists.

#9215 (comment)

I don't think AnimationTree should be used in the first place in cases where such a rotation is desired. It is doing attitude control, not blending, so it sounds like you want something that is clearly out of scope of "blending".

In short, we should not support it for the special cases on the assumption that flips will happen without proper reason. Then, the right way to go is to find a way to make it easier to set up rest/RESET.

The possibly valid justification here is compatibility with other game engines.

If other game engines support it on the assumption that the visual will break, then there is a chance that it could be implemented as an option with a name like “Unsteady Rotation”, where it is obvious that there will be a weird look to it.

Did a bunch of testing and reading last night, and I do think you're right that there's no general solution other than rest that prevents this sort of thing. There has got to be a more ergonomic solution than manually pulling the tracks and setting REST though. I'm not sure what that looks like, but I assume one has to already exist somewhere. I've been looking for similar issues across Unity and Unreal (I plan to actually test with these as well) to see if this was common behavior, but I couldn't find issues like this from either on help forums or issue trackers. Which of course doesn't really mean anything one way or another as it might be as simple as a difference in expectations of users.

@TokageItLab
Copy link
Member

TokageItLab commented Apr 28, 2024

I still believe that the fundamental problem is not the algorithm that depends on rest, but the UX problem where specifying rest is not easy.

In the case of your posture control, it might be possible to change rest depending on the blend position, i.e. keying not only pose but also rest into the animation, but depending on the iterating order of the AnimationNode, this might not work.

Also, there is no way to key Node3D::Trensform or 2D using the RESET track. This creates a dangerous double-structure state where the value of the animation key is changed by the animation key, in addition to the potential cache corruption caused by changes to the RESET animation.

In the end, setting rest/RESET in the blending process may be risky. I believe the better approach would be to consider an API that could easily change rest/RESET dynamically in the blend_init() stage before the blending process as UX improvement.

One idea for writing dynamic rests --iterating the current object state targeted by the rotation track before blending process and writing it to init_rotation, may be safer than dynamically retrieving the shortest path during blending. I will explore this when I have time;

I have exprole a bit, but the results are not good. At least, changing the rest to current rot every frame was not good due to value jumps elsewhere aside from the flips. Also I tried updating rest at specific timings depends on dot product, changing rest from the angular velocity of that frame, etc., but it was not stable. But I guess it would be a possible way to go that depending on the current rotation under some conditions to determine REST, so if we can find a way to dynamically determine REST without flips and jumps occurring, that should be an option that could be well implemented.

@huwpascoe
Copy link

Just came across this MIT licensed project.

https://github.com/guillaumeblanc/ozz-animation/

There might be some concepts in there we can use.

@MatthewBlanchard
Copy link

MatthewBlanchard commented Apr 30, 2024

Just came across this MIT licensed project.

https://github.com/guillaumeblanc/ozz-animation/

There might be some concepts in there we can use.

So from my read through the source of blendingjob.cc it seems like they're using rest differently and blending differently than Godot. I could definitely be wrong on this reading I'm not super confident I got all these broad strokes right or fully understand the context.

When processing the first animation in the blend the output rotation is set to that animation's rotation * it's blend weight. Then for each additional animation the rotations are accumulated with a componentwise addition of the animation's rotation quaternion * blend.

Then if the total weights are < some threshold the rest position is blended in. Then the quaternion is normalized. So an nlerp, essentially.

It also seems like they're checking the sign of the quaternions and flipping it if necessary to always head along the shortest path.

I'm going to test this out and see how it handles these cases.

@huwpascoe
Copy link

I'm not super confident I got all these broad strokes right

I think you have. Look at these quotes from the documentation (shortened here by me).

https://guillaumeblanc.github.io/ozz-animation/samples/blend/
"[the skeleton bind-pose] is used by the blending algorithm as a fall-back when the accumulated layer weight is too small"

https://guillaumeblanc.github.io/ozz-animation/samples/additive/
"The additive (or delta) animation is created by subtracting a reference pose from a source animation. It uses the first frame of the animation as the reference pose."

The rest pose is only applied once if needed, after blend layers, and before additive layers. Even if it doesn't fix the issue here, it'd still be a performance win for deleting a redundant vector multiplication that's currently done for every track.

@TokageItLab
Copy link
Member

TokageItLab commented Apr 30, 2024

Then if the total weights are < some threshold the rest position is blended in. Then the quaternion is normalized. So an nlerp, essentially.

I think the reason for using nlerp is that there is no commutativity problem. It is similar to that of exponential map.

An accumulation that does not have the specific quaternion (rest) as reference may cause the problem of different results depending on the iterating order: in Godot, it is AnimationNode's port index as I mentioned above.

It means that, in the iterative order A->B->C, when the shortest path of C to the blending result of A and B is clockwise, but in the iterative order C->B->A, the shortest path of A to the blending result of C and B may be counterclockwise.

It might solve the problem of ordering, but be aware that nlerp might not give the expected result if you want exactly the result of slerp, since nlerp result will be different from slerp.

some threshold

I assume this is the cases Deterministic is valid and total_weight of each blending is not 1.0 in Godot.

However, since the Deterministic option guarantees the validity of Additive Blending, it is impractical to introduce it there. So if blending is to be implemented without reference to rest by nlerp, it would need to be an option that can only be selected when Deterministic is disabled. In there, no need to blend with rest.

It sounds quite make sense that it is not Deterministic, if the blending result depends on the dynamic shortest distance; Deterministic requires that the AnimationTree results for the same blend state must always be equal, independent of the state of the object at the time.

But note that even if it is not Deterministic, changes in the result that depend on the current state of the object are acceptable, but not acceptable changes result due to differences in the port index of the AnimationNodes, if it will be, it is just a regression godotengine/godot#34134 (comment). I guess nlerp is one of the hint to avoid that.

It also seems like they're checking the sign of the quaternions and flipping it if necessary to always head along the shortest path.

I don't know if the flipping process there is for getting the shortest path, but note that if it depends on the result of comparing two quaternions of AnimationNodes, it may bring iterating order issues into the nlerp; If it compares to a quaternion that does not depend on the AnimationNode order, such as the rotation of the current object, there should be no problem.

Or, if the flip is to match the phase, then I think there is no problem since it should not like comparing two quaternions. In Godot, if you want to align quaternion phases, converting the quaternion to Basis and converting the Basis to quaternion again is the most stable and easiest way, although the performance is not so good.

@MatthewBlanchard
Copy link

MatthewBlanchard commented May 1, 2024

It doesn't seem to be working from the current/last rotation values at all. I don't see why their method would be non-deterministic.

All of the transformations are accumulated in a commutative way.
The output transform is not based on previous state or transform, only the animation parameters passed in.

After reading through a little more I don't see a reason why their entire process wouldn't be deterministic and give the exact same result given the same animations as parameters for blending.

The quaternions' signs are in fact being flipped to stay along the shortest path, supported by the authors comments on it in the code.

I've tried to capture their algorithm (greatly simplified/pared down) in this python-esque psuedocode:


function BlendLayers(args):
    for each layer in args.job.layers:
        if layer.weight <= 0:
            continue

        args.accumulated_weight += layer.weight
        layer_weight = layer.weight

        if args.num_passes == 0:
            for i = 0 to args.num_soa_joints:
                src = layer.transform[i]
                dest = args.job.output[i]
                dest = src * layer_weight
        else:
            for i = 0 to args.num_soa_joints:
                # the check for quaternion flipping happens here
                src = layer.transform[i]
                dest = args.job.output[i]
                dest += src * layer_weight

        args.num_passes++

function BlendRestPose(args):
    if args.num_partial_passes == 0:
        bp_weight = args.job.threshold - args.accumulated_weight

        if bp_weight > 0:
            if args.num_passes == 0:
                args.accumulated_weight = 1
                for i = 0 to args.num_soa_joints:
                    args.job.output[i] = args.job.rest_pose[i]
            else:
                args.accumulated_weight = args.job.threshold
                for i = 0 to args.num_soa_joints:
                    src = args.job.rest_pose[i]
                    dest = args.job.output[i]
                    dest += src * bp_weight

The BlendLayers() get's called and BlendRestPose() is called afterward, and then the quaternions are normalized for the final transform. I'm simplifying and handwaving a bit here, but this should cover the broad strokes. I think even the quaternion sign flips should all work out the same for any given order, but that's just intuition, I'm not confident enough in my understanding to support that. My thinking being if you're flipping the signs it doesn't matter what order it happens in you'll still have to do the same # of flips to always travel along the shortest path?

@TokageItLab
Copy link
Member

TokageItLab commented May 1, 2024

I am meaning about the shortest path may be different depending on the order in which the blend amounts are set.

For example, when blending all three quaternions below at 50%, the result is expected to vary (3 or 6 results should be expected) depending on the order in which they are set to 50% if the shortest path is always taken visually.

axis

In Deterministic, such a change in result will not occur because the final result depends on rest.

Also, since Deterministic is always performing Additive Blending internally, it is necessary to always set rest as a reference in order to accumulate rotations of 180 degrees or less with respect to rest. If the behavior of NodeAdd2 and NodeSub2 also works correctly with the nlerp method in Deterministic, it may be supportable, but it probably will not work correctly or will require complex calculations for the threshold.

Technically, even if it is Un-deterministic, it still performs Additive Blending internally, but then normalizes the weights to make the behavior consistent for cases where the total blend amount is not 1. Normalization guarantees that there will be no cases where the total weights are other than 1 or 0, resulting in the values of rest/RESET being ignored except for rotations. Here it still refers to rest/RESET for rotation, so it is expected that the nlerp method can be an additional option to remove that reference in the undeterministic option.

So currently, un-deterministic means that when the blend amount is 0, the state of the objects will not be rest/RESET, which means that the final result may differ depending on the current state of those objects. As explained above, if multiple results of rotation are expected depending on the visual state of the current object, then it should be un-deterministic.

In summary, changes in the result depending on the current state of the object are not allowed in deterministic, but are allowed in un-deterministic. Changes in the result depending on the iteration order of the AnimationNode are not allowed in either case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants