diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml
index b4c42ea399f7..86577032f07f 100644
--- a/doc/classes/Skeleton3D.xml
+++ b/doc/classes/Skeleton3D.xml
@@ -168,6 +168,13 @@
Returns whether the bone pose for the bone at [param bone_idx] is enabled.
+
+
+
+
+ Returns whether the bone should inherit the scale of its parent bone.
+
+
@@ -244,6 +251,14 @@
[b]Note:[/b] The pose transform needs to be a global pose! To convert a world transform from a [Node3D] to a global bone pose, multiply the [method Transform3D.affine_inverse] of the node's [member Node3D.global_transform] by the desired world transform.
+
+
+
+
+
+ Sets whether the parent's scale should be inherited by the bone.
+
+
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 20b91d8bfde0..7b3603714c06 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -91,6 +91,13 @@ void BoneTransformEditor::create_editors() {
scale_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
section->get_vbox()->add_child(scale_property);
+ // Inherit Scale property.
+ inherit_scale_property = memnew(EditorPropertyCheck());
+ inherit_scale_property->set_label("Inherit Scale");
+ inherit_scale_property->set_selectable(false);
+ inherit_scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
+ section->get_vbox()->add_child(inherit_scale_property);
+
// Transform/Matrix section.
rest_section = memnew(EditorInspectorSection);
rest_section->setup("trf_properties_transform", "Rest", this, Color(0.0f, 0.0f, 0.0f), true);
@@ -151,6 +158,9 @@ void BoneTransformEditor::set_target(const String &p_prop) {
scale_property->set_object_and_property(skeleton, p_prop + "scale");
scale_property->update_property();
+ inherit_scale_property->set_object_and_property(skeleton, p_prop + "inherit_scale");
+ inherit_scale_property->update_property();
+
rest_matrix->set_object_and_property(skeleton, p_prop + "rest");
rest_matrix->update_property();
}
@@ -207,6 +217,11 @@ void BoneTransformEditor::_update_properties() {
scale_property->update_property();
scale_property->queue_redraw();
}
+ if (split[2] == "inherit_bone") {
+ scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
+ scale_property->update_property();
+ scale_property->queue_redraw();
+ }
if (split[2] == "rest") {
rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rest_matrix->update_property();
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 3cc7c8549292..d6aa9c89eeda 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -59,6 +59,7 @@ class BoneTransformEditor : public VBoxContainer {
EditorPropertyVector3 *position_property = nullptr;
EditorPropertyQuaternion *rotation_property = nullptr;
EditorPropertyVector3 *scale_property = nullptr;
+ EditorPropertyCheck *inherit_scale_property = nullptr;
EditorInspectorSection *rest_section = nullptr;
EditorPropertyTransform3D *rest_matrix = nullptr;
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 445c1003b5dd..6010a7911e20 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -95,6 +95,8 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
set_bone_pose_rotation(which, p_value);
} else if (what == "scale") {
set_bone_pose_scale(which, p_value);
+ } else if (what == "inherit_scale") {
+ set_bone_inherit_scale(which, p_value);
} else {
return false;
}
@@ -128,6 +130,8 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
r_ret = get_bone_pose_rotation(which);
} else if (what == "scale") {
r_ret = get_bone_pose_scale(which);
+ } else if (what == "inherit_scale") {
+ r_ret = is_bone_inherit_scale(which);
} else {
return false;
}
@@ -145,6 +149,7 @@ void Skeleton3D::_get_property_list(List *p_list) const {
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("position"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + PNAME("rotation"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::BOOL, prep + PNAME("inherit_scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
for (PropertyInfo &E : *p_list) {
@@ -171,6 +176,9 @@ void Skeleton3D::_validate_property(PropertyInfo &p_property) const {
if (split[2] == "scale") {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
}
+ if (split[2] == "inherit_scale") {
+ p_property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
} else if (!is_bone_enabled(split[1].to_int())) {
if (split[2] == "position") {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
@@ -181,6 +189,9 @@ void Skeleton3D::_validate_property(PropertyInfo &p_property) const {
if (split[2] == "scale") {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
}
+ if (split[2] == "inherit_scale") {
+ p_property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
}
}
}
@@ -587,6 +598,23 @@ void Skeleton3D::set_bone_pose_scale(int p_bone, const Vector3 &p_scale) {
}
}
+bool Skeleton3D::is_bone_inherit_scale(int p_bone) const {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX_V(p_bone, bone_size, false);
+ return bones[p_bone].inherit_scale;
+}
+
+void Skeleton3D::set_bone_inherit_scale(int p_bone, bool inherit_scale) {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX(p_bone, bone_size);
+
+ bones.write[p_bone].inherit_scale = inherit_scale;
+ bones.write[p_bone].pose_cache_dirty = true;
+ if (is_inside_tree()) {
+ _make_dirty();
+ }
+}
+
Vector3 Skeleton3D::get_bone_pose_position(int p_bone) const {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, Vector3());
@@ -909,6 +937,21 @@ void Skeleton3D::force_update_all_bone_transforms() {
rest_dirty = false;
}
+// Applies transform to child while preserving child scale, and corrects rotation if inherit is false.
+// Otherwise, just does a regular transformation.
+Transform3D transform_local_scale(Transform3D parent, Transform3D child, bool inherit) {
+ if (inherit == true) {
+ return parent * child;
+ } else {
+ // apply full transform
+ Transform3D output = parent * child;
+
+ // replace basis with custom scaled
+ output.basis = Basis(parent.basis.get_rotation_quaternion() * child.basis.get_rotation_quaternion(), child.basis.get_scale());
+ return output;
+ }
+}
+
void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone_idx, bone_size);
@@ -929,23 +972,23 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
Transform3D pose = b.pose_cache;
if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global * pose;
- b.pose_global_no_override = bonesptr[b.parent].pose_global_no_override * pose;
+ b.pose_global = transform_local_scale(bonesptr[b.parent].pose_global, pose, b.inherit_scale);
+ b.pose_global_no_override = transform_local_scale(bonesptr[b.parent].pose_global_no_override, pose, b.inherit_scale);
} else {
b.pose_global = pose;
b.pose_global_no_override = pose;
}
} else {
if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global * b.rest;
- b.pose_global_no_override = bonesptr[b.parent].pose_global_no_override * b.rest;
+ b.pose_global = transform_local_scale(bonesptr[b.parent].pose_global, b.rest, b.inherit_scale);
+ b.pose_global_no_override = transform_local_scale(bonesptr[b.parent].pose_global_no_override, b.rest, b.inherit_scale);
} else {
b.pose_global = b.rest;
b.pose_global_no_override = b.rest;
}
}
if (rest_dirty) {
- b.global_rest = b.parent >= 0 ? bonesptr[b.parent].global_rest * b.rest : b.rest;
+ b.global_rest = b.parent >= 0 ? transform_local_scale(bonesptr[b.parent].global_rest, b.rest, b.inherit_scale) : b.rest;
}
if (b.global_pose_override_amount >= CMP_EPSILON) {
@@ -1000,6 +1043,9 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone_pose_rotation", "bone_idx", "rotation"), &Skeleton3D::set_bone_pose_rotation);
ClassDB::bind_method(D_METHOD("set_bone_pose_scale", "bone_idx", "scale"), &Skeleton3D::set_bone_pose_scale);
+ ClassDB::bind_method(D_METHOD("is_bone_inherit_scale", "bone_idx"), &Skeleton3D::is_bone_inherit_scale);
+ ClassDB::bind_method(D_METHOD("set_bone_inherit_scale", "bone_idx", "inherit_scale"), &Skeleton3D::set_bone_inherit_scale, DEFVAL(true));
+
ClassDB::bind_method(D_METHOD("get_bone_pose_position", "bone_idx"), &Skeleton3D::get_bone_pose_position);
ClassDB::bind_method(D_METHOD("get_bone_pose_rotation", "bone_idx"), &Skeleton3D::get_bone_pose_rotation);
ClassDB::bind_method(D_METHOD("get_bone_pose_scale", "bone_idx"), &Skeleton3D::get_bone_pose_scale);
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index 7d4df1d1f2b8..1c29626f17ab 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -91,6 +91,7 @@ class Skeleton3D : public Node3D {
Quaternion pose_rotation;
Vector3 pose_scale = Vector3(1, 1, 1);
+ bool inherit_scale = true;
Transform3D pose_global;
Transform3D pose_global_no_override;
@@ -192,6 +193,9 @@ class Skeleton3D : public Node3D {
void set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation);
void set_bone_pose_scale(int p_bone, const Vector3 &p_scale);
+ bool is_bone_inherit_scale(int p_bone) const;
+ void set_bone_inherit_scale(int p_bone, bool inherit_scale);
+
Transform3D get_bone_pose(int p_bone) const;
Vector3 get_bone_pose_position(int p_bone) const;