Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions doc/classes/BoneExpander3D.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="BoneExpander3D" inherits="SkeletonModifier3D" experimental="This SkeletonModifier3D interferes with the Skin in a slightly unsafe way. Performance and stability have not been sufficiently optimized." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Scales the visual appearance of the specified bone without causing shear in the children.
</brief_description>
<description>
Scales only the visual appearance without changing the bone pose scale. This is achieved by modifying the bone pose position and skin bindings instead of the bone pose scale. In other words, the only thing other modifiers can obtain from this modifier via the bone is the bone pose position.
If you use [BoneExpander3D] with [IKModifier3D] or [SpringBoneSimulator3D], you should enable [member IKModifier3D.mutable_bone_axes] and [member SpringBoneSimulator3D.mutable_bone_axes].
[b]Note:[/b] Changing the skin is not a role of the [SkeletonModifier3D] originally, so combining it with [RetargetModifier3D] or baking IK animation to FK animation may result in unintended behaviors.
</description>
<tutorials>
</tutorials>
<methods>
<method name="clear_setting">
<return type="void" />
<description>
Clears all settings.
</description>
</method>
<method name="get_bone" qualifiers="const">
<return type="int" />
<param index="0" name="index" type="int" />
<description>
Returns the scaled bone of the setting at [param index].
</description>
</method>
<method name="get_bone_name" qualifiers="const">
<return type="StringName" />
<param index="0" name="index" type="int" />
<description>
Returns the scaled bone name of the setting at [param index].
</description>
</method>
<method name="get_bone_scale" qualifiers="const">
<return type="Vector3" />
<param index="0" name="index" type="int" />
<description>
Returns the scale of the setting at [param index].
</description>
</method>
<method name="set_bone">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="bone" type="int" />
<description>
Sets the scaled bone of the setting at [param index].
</description>
</method>
<method name="set_bone_name">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="bone_name" type="StringName" />
<description>
Sets the scaled bone name of the setting at [param index].
</description>
</method>
<method name="set_bone_scale">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="scale" type="Vector3" />
<description>
[b]Note:[/b] The [param scale] value is recommended to be non-zero.
</description>
</method>
</methods>
<members>
<member name="setting_size" type="int" setter="set_setting_size" getter="get_setting_size" default="0">
The number of setting in the modifier.
</member>
</members>
</class>
7 changes: 6 additions & 1 deletion doc/classes/IKModifier3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
</brief_description>
<description>
Base class of [SkeletonModifier3D]s that has some joint lists and applies inverse kinematics. This class has some structs, enums, and helper methods which are useful to solve inverse kinematics.
[b]Note:[/b] The IK classes that extend this handle rotation only, with bone lengths cached. It means that a position movement between processed chains can cause unintended movement.
</description>
<tutorials>
</tutorials>
Expand Down Expand Up @@ -36,4 +35,10 @@
</description>
</method>
</methods>
<members>
<member name="mutable_bone_axes" type="bool" setter="set_mutable_bone_axes" getter="are_bone_axes_mutable" default="true">
If [code]true[/code], the solver retrieves the bone axis from the bone pose every frame.
If [code]false[/code], the solver retrieves the bone axis from the bone rest and caches it, which increases performance slightly, but position changes in the bone pose before processing this [IKModifier3D] are ignored.
</member>
</members>
</class>
4 changes: 4 additions & 0 deletions doc/classes/IterateIK3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@
The maximum amount each bone can rotate in a single iteration.
[b]Note:[/b] This limitation is applied during each iteration. For example, if [member max_iterations] is [code]4[/code] and [member angular_delta_limit] is [code]5[/code] degrees, the maximum rotation possible in a single frame is [code]20[/code] degrees.
</member>
<member name="deterministic" type="bool" setter="set_deterministic" getter="is_deterministic" default="false">
If [code]false[/code], the result is calculated from the previous frame's [IterateIK3D] result as the initial state.
If [code]true[/code], the previous frame's [IterateIK3D] result is discarded. At this point, the new result is calculated from the bone pose excluding the [IterateIK3D] as the initial state. This means the result will be always equal as long as the target position and the previous bone pose are the same. However, if [member angular_delta_limit] and [member max_iterations] are set too small, the end bone of the chain will never reach the target.
</member>
<member name="max_iterations" type="int" setter="set_max_iterations" getter="get_max_iterations" default="4">
The number of iteration loops used by the IK solver to produce more accurate results.
</member>
Expand Down
10 changes: 10 additions & 0 deletions doc/classes/Skeleton3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -412,12 +412,22 @@
Emitted when the value of [member show_rest_only] changes.
</description>
</signal>
<signal name="skeleton_rendered">
<description>
Emitted when the skins are rendered and modified poses are restored in the update process.
</description>
</signal>
<signal name="skeleton_updated">
<description>
Emitted when the final pose has been calculated will be applied to the skin in the update process.
This means that all [SkeletonModifier3D] processing is complete. In order to detect the completion of the processing of each [SkeletonModifier3D], use [signal SkeletonModifier3D.modification_processed].
</description>
</signal>
<signal name="skin_changed">
<description>
Emitted when the new skin is bound or bound skins are changed.
</description>
</signal>
</signals>
<constants>
<constant name="NOTIFICATION_UPDATE_SKELETON" value="50">
Expand Down
4 changes: 4 additions & 0 deletions doc/classes/SpringBoneSimulator3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,10 @@
The constant force that always affected bones. It is equal to the result when the parent [Skeleton3D] moves at this speed in the opposite direction.
This is useful for effects such as wind and anti-gravity.
</member>
<member name="mutable_bone_axes" type="bool" setter="set_mutable_bone_axes" getter="are_bone_axes_mutable" default="true">
If [code]true[/code], the solver retrieves the bone axis from the bone pose every frame.
If [code]false[/code], the solver retrieves the bone axis from the bone rest and caches it, which increases performance slightly, but position changes in the bone pose before processing this [SpringBoneSimulator3D] are ignored.
</member>
<member name="setting_count" type="int" setter="set_setting_count" getter="get_setting_count" default="0">
The number of settings.
</member>
Expand Down
1 change: 1 addition & 0 deletions editor/icons/BoneExpander3D.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 40 additions & 27 deletions editor/scene/3d/gizmos/chain_ik_3d_gizmo_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,18 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
int current_bone = -1;
int prev_bone = -1;
int joint_end = p_ik->get_joint_count(i) - 1;
float prev_length = INFINITY;
bool is_extended = p_ik->is_end_bone_extended(i) && p_ik->get_end_bone_length(i) > 0;
Transform3D anc_global_pose = p_ik->get_chain_root_global_rest(i);
for (int j = 0; j <= joint_end; j++) {
current_bone = p_ik->get_joint_bone(i, j);
Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone);
if (j > 0) {
int prev_joint = j - 1;
Transform3D parent_global_pose = p_skeleton->get_bone_global_rest(prev_bone);
draw_line(surface_tool, parent_global_pose.origin, global_pose.origin, bone_color);
Vector3 bone_vector = p_ik->get_bone_vector(i, prev_joint);
float current_length = bone_vector.length();
Vector3 center = parent_global_pose.translated_local(bone_vector).origin;
draw_line(surface_tool, parent_global_pose.origin, center, bone_color);

if (it_ik) {
// Draw rotation axis vector if not ROTATION_AXIS_ALL.
Expand All @@ -150,15 +155,14 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
if (rotation_axis != SkeletonModifier3D::ROTATION_AXIS_ALL) {
Vector3 axis_vector = it_ik->get_joint_rotation_axis_vector(i, j);
if (!axis_vector.is_zero_approx()) {
float rot_axis_length = (global_pose.origin - parent_global_pose.origin).length() * 0.2; // Use 20% of the bone length for the rotation axis vector.
Vector3 axis = global_pose.basis.xform(axis_vector.normalized()) * rot_axis_length;
draw_line(surface_tool, global_pose.origin - axis, global_pose.origin + axis, bone_color);
float rot_axis_length = bone_vector.length() * 0.2; // Use 20% of the bone length for the rotation axis vector.
Vector3 axis = parent_global_pose.basis.xform(axis_vector.normalized()) * rot_axis_length;
draw_line(surface_tool, center - axis, center + axis, bone_color);
}
}
}

// Draw parent limitation shape.
int prev_joint = j - 1;
Ref<JointLimitation3D> lim = it_ik->get_joint_limitation(i, prev_joint);
if (lim.is_valid()) {
// Limitation space should bind parent bone rest.
Expand All @@ -170,28 +174,36 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
surface_tool->set_weights(weights);
}
}
Transform3D tr = parent_global_pose;
Vector3 forward = p_skeleton->get_bone_rest(current_bone).origin;
tr.basis *= it_ik->get_joint_limitation_space(i, prev_joint, forward);
lim->draw_shape(surface_tool, tr, forward.length(), bone_color);
Vector3 x_axis = tr.basis.get_column(Vector3::AXIS_X).normalized() * forward.length() * 0.1;
Vector3 z_axis = tr.basis.get_column(Vector3::AXIS_Z).normalized() * forward.length() * 0.1;
Transform3D tr = anc_global_pose;
tr.basis *= it_ik->get_joint_limitation_space(i, prev_joint, bone_vector.normalized());
float sl = MIN(current_length, prev_length);
lim->draw_shape(surface_tool, tr, sl, bone_color);
sl *= 0.1;
Vector3 x_axis = tr.basis.get_column(Vector3::AXIS_X).normalized() * sl;
Vector3 z_axis = tr.basis.get_column(Vector3::AXIS_Z).normalized() * sl;
draw_line(surface_tool, tr.origin + x_axis * 2, tr.origin + x_axis * 3, limitation_x_axis_color); // Offset 20%.
draw_line(surface_tool, tr.origin + z_axis * 2, tr.origin + z_axis * 3, limitation_z_axis_color); // Offset 20%.
}
}
prev_length = current_length;
Transform3D tr = p_skeleton->get_bone_rest(current_bone);
tr.origin = bone_vector;
parent_global_pose *= tr;
anc_global_pose = parent_global_pose;
}
if (j == joint_end && is_extended) {
Vector3 axis = p_ik->get_bone_axis(current_bone, p_ik->get_end_bone_direction(i));
if (axis.is_zero_approx()) {
Transform3D current_global_pose = p_skeleton->get_bone_global_rest(current_bone);
Vector3 bone_vector = p_ik->get_bone_vector(i, j);
if (bone_vector.is_zero_approx()) {
continue;
}
float current_length = bone_vector.length();
bones.write[0] = current_bone;
surface_tool->set_bones(bones);
surface_tool->set_weights(weights);
float length = p_ik->get_end_bone_length(i);
axis = global_pose.xform(axis * length);
draw_line(surface_tool, global_pose.origin, axis, bone_color);
Vector3 center = current_global_pose.translated_local(bone_vector).origin;
draw_line(surface_tool, current_global_pose.origin, center, bone_color);

if (it_ik) {
// Draw limitation shape.
Ref<JointLimitation3D> lim = it_ik->get_joint_limitation(i, j);
Expand All @@ -205,12 +217,13 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
surface_tool->set_weights(weights);
}
}
Vector3 forward = it_ik->get_bone_axis(current_bone, it_ik->get_end_bone_direction(i));
Transform3D tr = global_pose;
tr.basis *= it_ik->get_joint_limitation_space(i, j, forward);
lim->draw_shape(surface_tool, tr, length, bone_color);
Vector3 x_axis = tr.basis.get_column(Vector3::AXIS_X).normalized() * length * 0.1;
Vector3 z_axis = tr.basis.get_column(Vector3::AXIS_Z).normalized() * length * 0.1;
Transform3D tr = anc_global_pose;
tr.basis *= it_ik->get_joint_limitation_space(i, j, bone_vector.normalized());
float sl = MIN(current_length, prev_length);
lim->draw_shape(surface_tool, tr, sl, bone_color);
sl *= 0.1;
Vector3 x_axis = tr.basis.get_column(Vector3::AXIS_X).normalized() * sl;
Vector3 z_axis = tr.basis.get_column(Vector3::AXIS_Z).normalized() * sl;
draw_line(surface_tool, tr.origin + x_axis * 2, tr.origin + x_axis * 3, limitation_x_axis_color); // Offset 20%.
draw_line(surface_tool, tr.origin + z_axis * 2, tr.origin + z_axis * 3, limitation_z_axis_color); // Offset 20%.
}
Expand All @@ -231,10 +244,10 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
if (rotation_axis != SkeletonModifier3D::ROTATION_AXIS_ALL) {
Vector3 axis_vector = it_ik->get_joint_rotation_axis_vector(i, j);
if (!axis_vector.is_zero_approx()) {
Transform3D next_bone_global_pose = p_skeleton->get_bone_global_rest(it_ik->get_joint_bone(i, 1));
float rot_axis_length = (next_bone_global_pose.origin - global_pose.origin).length() * 0.2; // Use 20% of the bone length for the rotation axis vector.
Vector3 axis = global_pose.basis.xform(axis_vector.normalized()) * rot_axis_length;
draw_line(surface_tool, global_pose.origin - axis, global_pose.origin + axis, bone_color);
Vector3 bone_vector = p_ik->get_bone_vector(i, j);
float rot_axis_length = bone_vector.length() * 0.2; // Use 20% of the bone length for the rotation axis vector.
Vector3 axis = anc_global_pose.basis.xform(axis_vector.normalized()) * rot_axis_length;
draw_line(surface_tool, anc_global_pose.origin - axis, anc_global_pose.origin + axis, bone_color);
}
}
}
Expand Down
18 changes: 10 additions & 8 deletions editor/scene/3d/gizmos/spring_bone_3d_gizmo_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,10 @@ Ref<ArrayMesh> SpringBoneSimulator3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_s
Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone);
if (j > 0) {
Transform3D parent_global_pose = p_skeleton->get_bone_global_rest(prev_bone);
draw_line(surface_tool, parent_global_pose.origin, global_pose.origin, bone_color);
draw_sphere(surface_tool, global_pose.basis, global_pose.origin, p_simulator->get_joint_radius(i, j - 1), bone_color);
Vector3 bone_vector = p_simulator->get_bone_vector(i, j - 1);
Vector3 center = parent_global_pose.translated_local(bone_vector).origin;
draw_line(surface_tool, parent_global_pose.origin, center, bone_color);
draw_sphere(surface_tool, global_pose.basis, center, p_simulator->get_joint_radius(i, j - 1), bone_color);

// Draw rotation axis vector if not ROTATION_AXIS_ALL.
if (j != joint_end || (j == joint_end && is_extended)) {
Expand All @@ -153,22 +155,22 @@ Ref<ArrayMesh> SpringBoneSimulator3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_s
if (!axis_vector.is_zero_approx()) {
float line_length = p_simulator->get_joint_radius(i, j - 1) * 2.0;
Vector3 axis = global_pose.basis.xform(axis_vector.normalized()) * line_length;
draw_line(surface_tool, global_pose.origin - axis, global_pose.origin + axis, bone_color);
draw_line(surface_tool, center - axis, center + axis, bone_color);
}
}
}
}
if (j == joint_end && is_extended) {
Vector3 axis = p_simulator->get_end_bone_axis(current_bone, p_simulator->get_end_bone_direction(i));
if (axis.is_zero_approx()) {
Vector3 bone_vector = p_simulator->get_bone_vector(i, j);
if (bone_vector.is_zero_approx()) {
continue;
}
bones[0] = current_bone;
surface_tool->set_bones(Vector<int>(bones));
surface_tool->set_weights(Vector<float>(weights));
axis = global_pose.xform(axis * p_simulator->get_end_bone_length(i));
draw_line(surface_tool, global_pose.origin, axis, bone_color);
draw_sphere(surface_tool, global_pose.basis, axis, p_simulator->get_joint_radius(i, j), bone_color);
Vector3 center = global_pose.translated_local(bone_vector).origin;
draw_line(surface_tool, global_pose.origin, center, bone_color);
draw_sphere(surface_tool, global_pose.basis, center, p_simulator->get_joint_radius(i, j), bone_color);
} else {
bones[0] = current_bone;
surface_tool->set_bones(Vector<int>(bones));
Expand Down
Loading