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

Make retarget keep global rest of unmapped bones if there are no mapped bones on the child #91560

Merged
merged 1 commit into from May 7, 2024

Conversation

ydeltastar
Copy link
Contributor

Fixes: #91558

I don't know why this was commented out, as it worked in every case I tested. Unmapped bones are unusable without it since we don't want retargeting to change their rests.

@ydeltastar ydeltastar requested a review from a team as a code owner May 4, 2024 14:35
@akien-mga akien-mga added this to the 4.3 milestone May 4, 2024
@akien-mga akien-mga requested a review from a team May 4, 2024 14:42
@fire fire requested a review from TokageItLab May 4, 2024 14:51
Copy link
Member

@fire fire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense, but I am not sure of any sideeffects.

Copy link
Member

@TokageItLab TokageItLab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why this was commented out, as it worked in every case I tested.

No. I remember I commented out them because the bones between the mapped bones have rest rotation on sharing animation in Godot 4 caused corrupting the animation sharing even if the those unmapped bones are not animating.

For example such case:
[LeftUpperArm] - [elbow_l(unmapped)] - [LeftLowerArm] - [wrist_l(unmapped)] - [LeftHand]

I think it's fine to keep their original rest if "there are no mapped bones in their children", so at least that check needs to be made.

@ydeltastar
Copy link
Contributor Author

ydeltastar commented May 5, 2024

For example such case:
[LeftUpperArm] - [elbow_l(unmapped)] - [LeftLowerArm] - [wrist_l(unmapped)] - [LeftHand

I did tests for cases like this and didn't find issues with keeping the axis. In fact, resetting it like before or implementing your suggestion breaks the animation when there are unmapped bones between mapped too.

I couldn't find any but isn't having issues due to irregular bone hierarchy when sharing animations the expected behavior? The user should fix the source or maybe they designed the rig like this on purpose. Handling it on the engine side seems to break everything else.

@TokageItLab
Copy link
Member

TokageItLab commented May 5, 2024

In Godot 4 animation, the child mapped rests must match the relative rotation from the parent mapped rest on retargeting.

You should test this in the case where the intermediate bone global rotation is different from the parent's global rotation (means that set rest other than Quat(0, 0, 0, 1)). To explain this in terms of Blender manipulation, you need to not only add a bone extended from its parent, but also add a bone in between that has been extended and has a modified roll or tail1. And test to create animations with them individually, then if they can share with each other.

So if you only apply keep global rest when there is no child mapped bone, this change is acceptable, but otherwise we need a workaround with another module such as real time retarget to keep global rest.

Footnotes

  1. While this case is probably not a frequent occurrence in blender operations, it may well occur in other software

@ydeltastar
Copy link
Contributor Author

Okay, now I understand what issue and I could reproduce it. I implemented your request and now this fix is working for both cases.

@TokageItLab TokageItLab changed the title Fix axis reset of unmapped bones on retargeting Make retarget to keep global rest of unmapped bones if there are no mapped bones on the child May 5, 2024
Copy link
Member

@TokageItLab TokageItLab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@lyuma lyuma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of code, it's ok. This is haphazardly changing the rest rotation for some non-human bones, and I think the logic seems a little non-obvious. For example, it checks if a child is mapped, but what about grandchildren.

Will a user understand why a "elbow twist" bone has rotation changed while a "elbow sleeve" bone keeps the original rotation?

Furthermore, there may be users who have created BoneAttachments using the identity Basis and adjusted for it either in code or by manually positioning attached objects. Therefore, this risks compatibility break with any existing scenes / bone attachments. which were made assuming the previous design (which has stayed the same since Godot 4.0 was released)

With Godot 4.3 about to hit beta, this is quite late in the release cycle to be changing a fundamental part of the retargeting design.

At the very least, if we decide to go forward with this change, we need to include a compatibility setting on the retarget settings (Skeleton3D) which defaults to the old behavior for existing files and a different setting for new files.

Copy link
Member

@TokageItLab TokageItLab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, sorry I missed that it only looks at the children and not the grandchildren.

As long as we don't reimport them, there should be no compatibility issues, but I don't know how to handle compatibility as I don't have a strong opinion for that.

At least if you change it to check its grandchildren, I still can approve this, IMO. For performance, it would better to scan from parentress_bone to pre-create a whitelist with no mapped descendants and check if the rest rewriting bones are included in it.

@AThousandShips AThousandShips changed the title Make retarget to keep global rest of unmapped bones if there are no mapped bones on the child Make retarget keep global rest of unmapped bones if there are no mapped bones on the child May 5, 2024
@ydeltastar ydeltastar force-pushed the retarget-axis-fix branch 3 times, most recently from 1cfbae3 to d6d9a4c Compare May 5, 2024 19:31
@ydeltastar
Copy link
Contributor Author

ydeltastar commented May 5, 2024

At the very least, if we decide to go forward with this change, we need to include a compatibility setting on the retarget settings (Skeleton3D) which defaults to the old behavior for existing files and a different setting for new files.

It was my first intention to make it an option when I went on this but I thought this was a bug. Since it is a wanted behavior for the importer, I added a Keep Unmapped Axis default to false (a strange default though) and the check for mapped descendants.

@TokageItLab
Copy link
Member

TokageItLab commented May 6, 2024

For intermediate bones, Keep Unmapped Axis is strange because it is discarded even if it is axis ummapped, so it would be a very special property like compatibility mode. But I personally do not think this option is necessary. I think it's a pretty rare case where this would cause a problem, also that problem does not occur unless a re-import occurs.

@lyuma
Copy link
Contributor

lyuma commented May 6, 2024

Wouldn't this affect every bone attachment on retargeted skeletons in every existing godot project? I'm not convinced that this is rare.

Also, about reimport, we can't assume that they don't happen. In fact, the whole .godot directory is excluded from git and users are told to delete that directory when there is a problem or if reporting a bug, so it is almost guaranteed that any issues which occur on reimport will eventually occur for a user.

@TokageItLab
Copy link
Member

TokageItLab commented May 6, 2024

The values of the bone attachments may be changed, but I expect that in cases where the original rest is discarded due to override rest, the user will notice that something is strange, like the author of this PR, and therefore it is unusual to create a project that highly depending on that rest.

@ydeltastar ydeltastar force-pushed the retarget-axis-fix branch 2 times, most recently from 9392808 to fe12553 Compare May 6, 2024 16:21
Copy link
Contributor

@lyuma lyuma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With an import option here it's ok.

Note: queue vector .erase(), whitelist vector .has() and nested for loop are all order N^2 time complexity. This is an endemic problem with the Godot import code and not isolated to this codepath so I'll approve anyway.

@ydeltastar ydeltastar force-pushed the retarget-axis-fix branch 2 times, most recently from 4d606a7 to b400b44 Compare May 7, 2024 11:10
@akien-mga akien-mga requested a review from TokageItLab May 7, 2024 11:38
Comment on lines 57 to 71
Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
if (p_option == "retarget/rest_fixer/keep_global_rest_on_leftovers") {
return bool(p_options["retarget/rest_fixer/overwrite_axis"]);
}
}
return true;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks a bit inconsistent with other similar parts in terms of the order of checks, but since the result is not different, it should not be a problem.

@ydeltastar
Copy link
Contributor Author

Rebased with master.

@akien-mga akien-mga merged commit 55b8724 into godotengine:master May 7, 2024
16 checks passed
@akien-mga
Copy link
Member

Thanks!

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

Successfully merging this pull request may close these issues.

Retargeting resets orientation of unmapped bones when Overwrite Axis is enabled
6 participants