diff --git a/doc/classes/SpringBoneCollision3D.xml b/doc/classes/SpringBoneCollision3D.xml index ea4556674cf..85738b66971 100644 --- a/doc/classes/SpringBoneCollision3D.xml +++ b/doc/classes/SpringBoneCollision3D.xml @@ -7,6 +7,7 @@ A collision can be a child of [SpringBoneSimulator3D]. If it is not a child of [SpringBoneSimulator3D], it has no effect. The colliding and sliding are done in the [SpringBoneSimulator3D]'s modification process in order of its collision list which is set by [method SpringBoneSimulator3D.set_collision_path]. If [method SpringBoneSimulator3D.are_all_child_collisions_enabled] is [code]true[/code], the order matches [SceneTree]. If [member bone] is set, it synchronizes with the bone pose of the ancestor [Skeleton3D], which is done in before the [SpringBoneSimulator3D]'s modification process as the pre-process. + [b]Warning:[/b] A scaled [SpringBoneCollision3D] will likely not behave as expected. Make sure that the parent [Skeleton3D] and its bones are not scaled. diff --git a/doc/classes/SpringBoneSimulator3D.xml b/doc/classes/SpringBoneSimulator3D.xml index 6c639105d9e..1748933fbca 100644 --- a/doc/classes/SpringBoneSimulator3D.xml +++ b/doc/classes/SpringBoneSimulator3D.xml @@ -10,6 +10,7 @@ Several properties can be applied to each joint, such as [method set_joint_stiffness], [method set_joint_drag], and [method set_joint_gravity]. For simplicity, you can set values to all joints at the same time by using a [Curve]. If you want to specify detailed values individually, set [method set_individual_config] to [code]true[/code]. For physical simulation, [SpringBoneSimulator3D] can have children as self-standing collisions that are not related to [PhysicsServer3D], see also [SpringBoneCollision3D]. + [b]Warning:[/b] A scaled [SpringBoneSimulator3D] will likely not behave as expected. Make sure that the parent [Skeleton3D] and its bones are not scaled. @@ -583,6 +584,7 @@ Sets the rotation axis of the bone chain. If sets a specific axis, it acts like a hinge joint. The value is cached in each joint setting in the joint list. + [b]Note:[/b] The rotation axis and the forward vector shouldn't be colinear to avoid unintended rotation since [SpringBoneSimulator3D] does not factor in twisting forces. diff --git a/scene/3d/spring_bone_simulator_3d.cpp b/scene/3d/spring_bone_simulator_3d.cpp index db2755288bf..494033220e8 100644 --- a/scene/3d/spring_bone_simulator_3d.cpp +++ b/scene/3d/spring_bone_simulator_3d.cpp @@ -939,6 +939,10 @@ void SpringBoneSimulator3D::set_joint_rotation_axis(int p_index, int p_joint, Ro Vector &joints = settings[p_index]->joints; ERR_FAIL_INDEX(p_joint, joints.size()); joints[p_joint]->rotation_axis = p_axis; + Skeleton3D *sk = get_skeleton(); + if (sk) { + _validate_rotation_axis(sk, p_index, p_joint); + } } SpringBoneSimulator3D::RotationAxis SpringBoneSimulator3D::get_joint_rotation_axis(int p_index, int p_joint) const { @@ -1252,6 +1256,35 @@ void SpringBoneSimulator3D::remove_child_notify(Node *p_child) { } } +void SpringBoneSimulator3D::_validate_rotation_axes(Skeleton3D *p_skeleton) const { + for (int i = 0; i < settings.size(); i++) { + for (int j = 0; j < settings[i]->joints.size(); j++) { + _validate_rotation_axis(p_skeleton, i, j); + } + } +} + +void SpringBoneSimulator3D::_validate_rotation_axis(Skeleton3D *p_skeleton, int p_index, int p_joint) const { + RotationAxis axis = settings[p_index]->joints[p_joint]->rotation_axis; + if (axis == ROTATION_AXIS_ALL) { + return; + } + Vector3 rot = get_vector_from_axis(static_cast((int)axis)); + Vector3 fwd; + if (p_joint < settings[p_index]->joints.size() - 1) { + fwd = p_skeleton->get_bone_rest(settings[p_index]->joints[p_joint + 1]->bone).origin; + } else if (settings[p_index]->extend_end_bone) { + fwd = get_end_bone_axis(settings[p_index]->end_bone, settings[p_index]->end_bone_direction); + if (fwd.is_zero_approx()) { + return; + } + } + fwd.normalize(); + if (Math::is_equal_approx(Math::absf(rot.dot(fwd)), 1.0f)) { + WARN_PRINT_ED("Setting: " + itos(p_index) + " Joint: " + itos(p_joint) + ": Rotation axis and forward vectors are colinear. This is not advised as it may cause unwanted rotation."); + } +} + void SpringBoneSimulator3D::_find_collisions() { if (!collisions_dirty) { return; @@ -1422,6 +1455,10 @@ void SpringBoneSimulator3D::_update_joints() { settings[i]->joints_dirty = false; } joints_dirty = false; + Skeleton3D *sk = get_skeleton(); + if (sk) { + _validate_rotation_axes(sk); + } #ifdef TOOLS_ENABLED update_gizmos(); #endif // TOOLS_ENABLED diff --git a/scene/3d/spring_bone_simulator_3d.h b/scene/3d/spring_bone_simulator_3d.h index 9c9aa913736..15d373e9a6e 100644 --- a/scene/3d/spring_bone_simulator_3d.h +++ b/scene/3d/spring_bone_simulator_3d.h @@ -163,6 +163,9 @@ protected: virtual void move_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; + void _validate_rotation_axes(Skeleton3D *p_skeleton) const; + void _validate_rotation_axis(Skeleton3D *p_skeleton, int p_index, int p_joint) const; + public: // Setting. void set_root_bone_name(int p_index, const String &p_bone_name);