-
-
Notifications
You must be signed in to change notification settings - Fork 21.8k
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
Setting scale X or Y (not both) to negative value on Skeleton2D breaks Modifications like SkeletonModification2DLookAt and SkeletonModification2DTwoBoneIK #80252
Comments
I think that most problems have to do with using transform and not restoring rotation and scale. It's not possible to represent negative X scale with transforms, it needs to restore this information from the local scale/rotation and not from the transform (I couldn't find how Node2D normally does that restoration). So when the godot/scene/2d/skeleton_2d.cpp Line 170 in 7ba79d6
godot/scene/2d/skeleton_2d.cpp Line 183 in 7ba79d6
Screencast.from.2023-08-13.09-23-19.webmI did some testing with caching the scale and rotation (my own way because i don't understand how Node2D restore this information). Screencast.from.2023-08-13.09-24-17.webmNote that after creating a https://github.com/godotengine/godot/blob/master/scene/2d/skeleton_2d.cpp#L618 As is always flipping the Y scale and the rotation, this probably cause most of the problems with Note: I could be wrong 🤣 |
And I was wrong. I started looking at the math behind On rangejoint_one_bone->set_global_rotation(angle_atan - angle_0 - joint_one_bone->get_bone_angle()); And my guess is that when you scale X by -1 you should stop subtracting arctangent by the internal angle and start adding it. joint_one_bone->set_global_rotation(angle_atan + angle_0 + joint_one_bone->get_bone_angle()); Out of rangejoint_one_bone->set_global_rotation(angle_atan - joint_one_bone->get_bone_angle());
joint_two_bone->set_global_rotation(angle_atan - joint_two_bone->get_bone_angle()); After scaling X by -1, bone angles start rotating to the opposite direction. joint_one_bone->set_global_rotation(angle_atan + joint_one_bone->get_bone_angle());
joint_two_bone->set_global_rotation(angle_atan + joint_two_bone->get_bone_angle()); Bugfloat bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y);
float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y); It should be using absolute values. Otherwise:
float bone_one_length = joint_one_bone->get_length() * MIN(ABS(joint_one_bone->get_global_scale().x), ABS(joint_one_bone->get_global_scale().y));
float bone_two_length = joint_two_bone->get_length() * MIN(ABS(joint_two_bone->get_global_scale().x), ABS(joint_two_bone->get_global_scale().y)); TestsI only did manual tests, so I could be wrong (again). Screencast.from.2023-08-20.08-35-40.webmcode:: master...thiagola92:godot:fix_twoboneik_with_negative_scale |
Looks good to me, you can open a PR to start getting reviews from other devs. Side note: wow, checked the reference links above the modified code and found a nice article and blog! Also found a typo |
Yes! This article and blog were very helpful! I still have to look at the Gizmo, not sure if was suppose to rotate this way after scaling. 😆 Screencast.from.2023-08-23.13-15-23.webmEdit: Fixed, i will create PR |
Flipping collision shapes is not supported, so the sub components must be flipped. However Godot skeletons IK's break when flipped. So character flipping is not possible right now without creating a separate player skeleton and linked components for each direction. I do not know if this is intended behavior or a bug, but it is documented here: godotengine/godot#75224 godotengine/godot#80252
Can this fix be added to version 4.3? |
If someone's interested in a temporary solution, I have created a script to do the two bone IK calculations, respecting the transform of the skeleton. It's still quite limited, as the scale values can be only 1 or -1, as neither bone length is "scaled" nor the distance between the bone origin and the target node. I based my implementation on In summary - with some tweaks I managed to project the target position onto the bone's local space, this allows to compensate the scale and rotation of the bone's parent, now scaling and rotating the skeleton should work fine (works for me at least). twoboneik.webmextends Node
@export_node_path("Node2D")
var target_node_path = NodePath()
@export var flip_bend_direction = false
@export var joint_one_bone_index = -1
@export var joint_two_bone_index = -1
var _angle_a = 0.0
var _angle_b = 0.0
func _process(delta: float) -> void:
_update_two_bone_ik_angles()
func _update_two_bone_ik_angles():
assert(joint_one_bone_index != -1)
assert(joint_two_bone_index != -1)
if target_node_path.is_empty():
return
var target = get_node(target_node_path) as Node2D
var bone_a = get_parent().get_bone(joint_one_bone_index)
var bone_b = get_parent().get_bone(joint_two_bone_index)
var bone_a_len = bone_a.get_length()
var bone_b_len = bone_b.get_length()
var sin_angle2 = 0.0
var cos_angle2 = 1.0
_angle_b = 0.0
var cos_angle2_denom = 2.0 * bone_a_len * bone_b_len
if not is_zero_approx(cos_angle2_denom):
var target_len_sqr = _distance_squared_between(bone_a, target)
var bone_a_len_sqr = bone_a_len * bone_a_len
var bone_b_len_sqr = bone_b_len * bone_b_len
cos_angle2 = (target_len_sqr - bone_a_len_sqr - bone_b_len_sqr) / cos_angle2_denom
cos_angle2 = clamp(cos_angle2, -1.0, 1.0);
_angle_b = acos(cos_angle2)
if flip_bend_direction:
_angle_b = -_angle_b
sin_angle2 = sin(_angle_b)
var tri_adjacent = bone_a_len + bone_b_len * cos_angle2
var tri_opposite = bone_b_len * sin_angle2
var xform_inv = bone_a.get_parent().global_transform.affine_inverse()
var target_pos = xform_inv * target.global_position - bone_a.position
var tan_y = target_pos.y * tri_adjacent - target_pos.x * tri_opposite
var tan_x = target_pos.x * tri_adjacent + target_pos.y * tri_opposite
_angle_a = atan2(tan_y, tan_x)
var bone_a_angle = bone_a.get_bone_angle()
var bone_b_angle = bone_b.get_bone_angle()
bone_a.rotation = _angle_a - bone_a_angle
bone_b.rotation = _angle_b - angle_difference(bone_a_angle, bone_b_angle)
func _distance_squared_between(node_a: Node2D, node_b: Node2D) -> float:
return node_a.global_position.distance_squared_to(node_b.global_position) |
Godot version
v4.1.stable.official [9704596]
System information
Godot v4.1.stable - Ubuntu 22.04.2 LTS 22.04 - Vulkan (Mobile) - dedicated NVIDIA GeForce GTX 860M (nvidia; 535.54.03) - Intel(R) Core(TM) i7-4710HQ CPU @ 2.50GHz (8 Threads)
Issue description
Context
I'm working with a 2D platformer character who flips around horizontally when going left, using a full scale X = -1 on a parent containing all visual nodes, including the Skeleton2D. The Skeleton2D uses a 2-bone IK modification. I noticed that when setting scale X = -1 on the Skeleton 2D (whether the IK target is also scaled or not), the bones didn't behave as expected.
General issue
When a Skeleton2D (or some parent of it) is scaled on X or Y (but not both) with a negative value, modifications such as SkeletonModification2DLookAt and SkeletonModification2DTwoBoneIK do not behave as expected: bones go the opposite way of intended, as if moving away from the target.
Example with an actual character using 2-bone IK on arms:
Normal pose, arm-hand using 2-bone IK toward two targets (red crosses):

Scale X = -1, including IK targets to flip them so they stay near corresponding bones:

Scale X = -1, excluding IK targets:

Note that three phenomena occur:
Another smaller example that you'll find in the MWE attached:
Normal pose (uses 2-bone IK + Look At for another bone):

Scale X = -1:

Scale Y = -1:

This time, LookAt is easy t understand, 2-bone IK much less as the bones shrink on themselves.
Steps to reproduce
Minimal reproduction project
Godot 4.1 - Scaling skeleton X=-1 causes 2BoneIK to avoid target.zip
The text was updated successfully, but these errors were encountered: