Skip to content

Commit 031fd66

Browse files
committed
Add mutable_bone_axes to IKs
1 parent ef34c3d commit 031fd66

18 files changed

+760
-155
lines changed

doc/classes/IKModifier3D.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
</brief_description>
66
<description>
77
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.
8-
[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.
98
</description>
109
<tutorials>
1110
</tutorials>
@@ -36,4 +35,10 @@
3635
</description>
3736
</method>
3837
</methods>
38+
<members>
39+
<member name="mutable_bone_axes" type="bool" setter="set_mutable_bone_axes" getter="are_bone_axes_mutable" default="true">
40+
If [code]true[/code], the solver retrieves the bone axis from the bone pose every frame.
41+
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 made before processing this [IKModifier3D] are ignored.
42+
</member>
43+
</members>
3944
</class>

doc/classes/SpringBoneSimulator3D.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,10 @@
637637
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.
638638
This is useful for effects such as wind and anti-gravity.
639639
</member>
640+
<member name="mutable_bone_axes" type="bool" setter="set_mutable_bone_axes" getter="are_bone_axes_mutable" default="true">
641+
If [code]true[/code], the solver retrieves the bone axis from the bone pose every frame.
642+
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 made before processing this [SpringBoneSimulator3D] are ignored.
643+
</member>
640644
<member name="setting_count" type="int" setter="set_setting_count" getter="get_setting_count" default="0">
641645
The number of settings.
642646
</member>

editor/scene/3d/gizmos/chain_ik_3d_gizmo_plugin.cpp

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,18 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
135135
int current_bone = -1;
136136
int prev_bone = -1;
137137
int joint_end = p_ik->get_joint_count(i) - 1;
138+
float prev_length = INFINITY;
138139
bool is_extended = p_ik->is_end_bone_extended(i) && p_ik->get_end_bone_length(i) > 0;
140+
Transform3D anc_global_pose = p_ik->get_chain_root_global_rest(i);
139141
for (int j = 0; j <= joint_end; j++) {
140142
current_bone = p_ik->get_joint_bone(i, j);
141-
Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone);
142143
if (j > 0) {
144+
int prev_joint = j - 1;
143145
Transform3D parent_global_pose = p_skeleton->get_bone_global_rest(prev_bone);
144-
draw_line(surface_tool, parent_global_pose.origin, global_pose.origin, bone_color);
146+
Vector3 bone_vector = p_ik->get_bone_vector(i, prev_joint);
147+
float current_length = bone_vector.length();
148+
Vector3 center = parent_global_pose.translated_local(bone_vector).origin;
149+
draw_line(surface_tool, parent_global_pose.origin, center, bone_color);
145150

146151
if (it_ik) {
147152
// Draw rotation axis vector if not ROTATION_AXIS_ALL.
@@ -150,15 +155,14 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
150155
if (rotation_axis != SkeletonModifier3D::ROTATION_AXIS_ALL) {
151156
Vector3 axis_vector = it_ik->get_joint_rotation_axis_vector(i, j);
152157
if (!axis_vector.is_zero_approx()) {
153-
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.
154-
Vector3 axis = global_pose.basis.xform(axis_vector.normalized()) * rot_axis_length;
155-
draw_line(surface_tool, global_pose.origin - axis, global_pose.origin + axis, bone_color);
158+
float rot_axis_length = bone_vector.length() * 0.2; // Use 20% of the bone length for the rotation axis vector.
159+
Vector3 axis = parent_global_pose.basis.xform(axis_vector.normalized()) * rot_axis_length;
160+
draw_line(surface_tool, center - axis, center + axis, bone_color);
156161
}
157162
}
158163
}
159164

160165
// Draw parent limitation shape.
161-
int prev_joint = j - 1;
162166
Ref<JointLimitation3D> lim = it_ik->get_joint_limitation(i, prev_joint);
163167
if (lim.is_valid()) {
164168
// Limitation space should bind parent bone rest.
@@ -170,28 +174,36 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
170174
surface_tool->set_weights(weights);
171175
}
172176
}
173-
Transform3D tr = parent_global_pose;
174-
Vector3 forward = p_skeleton->get_bone_rest(current_bone).origin;
175-
tr.basis *= it_ik->get_joint_limitation_space(i, prev_joint, forward);
176-
lim->draw_shape(surface_tool, tr, forward.length(), bone_color);
177-
Vector3 x_axis = tr.basis.get_column(Vector3::AXIS_X).normalized() * forward.length() * 0.1;
178-
Vector3 z_axis = tr.basis.get_column(Vector3::AXIS_Z).normalized() * forward.length() * 0.1;
177+
Transform3D tr = anc_global_pose;
178+
tr.basis *= it_ik->get_joint_limitation_space(i, prev_joint, bone_vector.normalized());
179+
float sl = MIN(current_length, prev_length);
180+
lim->draw_shape(surface_tool, tr, sl, bone_color);
181+
sl *= 0.1;
182+
Vector3 x_axis = tr.basis.get_column(Vector3::AXIS_X).normalized() * sl;
183+
Vector3 z_axis = tr.basis.get_column(Vector3::AXIS_Z).normalized() * sl;
179184
draw_line(surface_tool, tr.origin + x_axis * 2, tr.origin + x_axis * 3, limitation_x_axis_color); // Offset 20%.
180185
draw_line(surface_tool, tr.origin + z_axis * 2, tr.origin + z_axis * 3, limitation_z_axis_color); // Offset 20%.
181186
}
182187
}
188+
prev_length = current_length;
189+
Transform3D tr = p_skeleton->get_bone_rest(current_bone);
190+
tr.origin = bone_vector;
191+
parent_global_pose *= tr;
192+
anc_global_pose = parent_global_pose;
183193
}
184194
if (j == joint_end && is_extended) {
185-
Vector3 axis = p_ik->get_bone_axis(current_bone, p_ik->get_end_bone_direction(i));
186-
if (axis.is_zero_approx()) {
195+
Transform3D current_global_pose = p_skeleton->get_bone_global_rest(current_bone);
196+
Vector3 bone_vector = p_ik->get_bone_vector(i, j);
197+
if (bone_vector.is_zero_approx()) {
187198
continue;
188199
}
200+
float current_length = bone_vector.length();
189201
bones.write[0] = current_bone;
190202
surface_tool->set_bones(bones);
191203
surface_tool->set_weights(weights);
192-
float length = p_ik->get_end_bone_length(i);
193-
axis = global_pose.xform(axis * length);
194-
draw_line(surface_tool, global_pose.origin, axis, bone_color);
204+
Vector3 center = current_global_pose.translated_local(bone_vector).origin;
205+
draw_line(surface_tool, current_global_pose.origin, center, bone_color);
206+
195207
if (it_ik) {
196208
// Draw limitation shape.
197209
Ref<JointLimitation3D> lim = it_ik->get_joint_limitation(i, j);
@@ -205,12 +217,13 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
205217
surface_tool->set_weights(weights);
206218
}
207219
}
208-
Vector3 forward = it_ik->get_bone_axis(current_bone, it_ik->get_end_bone_direction(i));
209-
Transform3D tr = global_pose;
210-
tr.basis *= it_ik->get_joint_limitation_space(i, j, forward);
211-
lim->draw_shape(surface_tool, tr, length, bone_color);
212-
Vector3 x_axis = tr.basis.get_column(Vector3::AXIS_X).normalized() * length * 0.1;
213-
Vector3 z_axis = tr.basis.get_column(Vector3::AXIS_Z).normalized() * length * 0.1;
220+
Transform3D tr = anc_global_pose;
221+
tr.basis *= it_ik->get_joint_limitation_space(i, j, bone_vector.normalized());
222+
float sl = MIN(current_length, prev_length);
223+
lim->draw_shape(surface_tool, tr, sl, bone_color);
224+
sl *= 0.1;
225+
Vector3 x_axis = tr.basis.get_column(Vector3::AXIS_X).normalized() * sl;
226+
Vector3 z_axis = tr.basis.get_column(Vector3::AXIS_Z).normalized() * sl;
214227
draw_line(surface_tool, tr.origin + x_axis * 2, tr.origin + x_axis * 3, limitation_x_axis_color); // Offset 20%.
215228
draw_line(surface_tool, tr.origin + z_axis * 2, tr.origin + z_axis * 3, limitation_z_axis_color); // Offset 20%.
216229
}
@@ -231,10 +244,10 @@ Ref<ArrayMesh> ChainIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, Cha
231244
if (rotation_axis != SkeletonModifier3D::ROTATION_AXIS_ALL) {
232245
Vector3 axis_vector = it_ik->get_joint_rotation_axis_vector(i, j);
233246
if (!axis_vector.is_zero_approx()) {
234-
Transform3D next_bone_global_pose = p_skeleton->get_bone_global_rest(it_ik->get_joint_bone(i, 1));
235-
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.
236-
Vector3 axis = global_pose.basis.xform(axis_vector.normalized()) * rot_axis_length;
237-
draw_line(surface_tool, global_pose.origin - axis, global_pose.origin + axis, bone_color);
247+
Vector3 bone_vector = p_ik->get_bone_vector(i, j);
248+
float rot_axis_length = bone_vector.length() * 0.2; // Use 20% of the bone length for the rotation axis vector.
249+
Vector3 axis = anc_global_pose.basis.xform(axis_vector.normalized()) * rot_axis_length;
250+
draw_line(surface_tool, anc_global_pose.origin - axis, anc_global_pose.origin + axis, bone_color);
238251
}
239252
}
240253
}

editor/scene/3d/gizmos/spring_bone_3d_gizmo_plugin.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,10 @@ Ref<ArrayMesh> SpringBoneSimulator3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_s
142142
Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone);
143143
if (j > 0) {
144144
Transform3D parent_global_pose = p_skeleton->get_bone_global_rest(prev_bone);
145-
draw_line(surface_tool, parent_global_pose.origin, global_pose.origin, bone_color);
146-
draw_sphere(surface_tool, global_pose.basis, global_pose.origin, p_simulator->get_joint_radius(i, j - 1), bone_color);
145+
Vector3 bone_vector = p_simulator->get_bone_vector(i, j - 1);
146+
Vector3 center = parent_global_pose.translated_local(bone_vector).origin;
147+
draw_line(surface_tool, parent_global_pose.origin, center, bone_color);
148+
draw_sphere(surface_tool, global_pose.basis, center, p_simulator->get_joint_radius(i, j - 1), bone_color);
147149

148150
// Draw rotation axis vector if not ROTATION_AXIS_ALL.
149151
if (j != joint_end || (j == joint_end && is_extended)) {
@@ -153,22 +155,22 @@ Ref<ArrayMesh> SpringBoneSimulator3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_s
153155
if (!axis_vector.is_zero_approx()) {
154156
float line_length = p_simulator->get_joint_radius(i, j - 1) * 2.0;
155157
Vector3 axis = global_pose.basis.xform(axis_vector.normalized()) * line_length;
156-
draw_line(surface_tool, global_pose.origin - axis, global_pose.origin + axis, bone_color);
158+
draw_line(surface_tool, center - axis, center + axis, bone_color);
157159
}
158160
}
159161
}
160162
}
161163
if (j == joint_end && is_extended) {
162-
Vector3 axis = p_simulator->get_end_bone_axis(current_bone, p_simulator->get_end_bone_direction(i));
163-
if (axis.is_zero_approx()) {
164+
Vector3 bone_vector = p_simulator->get_bone_vector(i, j);
165+
if (bone_vector.is_zero_approx()) {
164166
continue;
165167
}
166168
bones[0] = current_bone;
167169
surface_tool->set_bones(Vector<int>(bones));
168170
surface_tool->set_weights(Vector<float>(weights));
169-
axis = global_pose.xform(axis * p_simulator->get_end_bone_length(i));
170-
draw_line(surface_tool, global_pose.origin, axis, bone_color);
171-
draw_sphere(surface_tool, global_pose.basis, axis, p_simulator->get_joint_radius(i, j), bone_color);
171+
Vector3 center = global_pose.translated_local(bone_vector).origin;
172+
draw_line(surface_tool, global_pose.origin, center, bone_color);
173+
draw_sphere(surface_tool, global_pose.basis, center, p_simulator->get_joint_radius(i, j), bone_color);
172174
} else {
173175
bones[0] = current_bone;
174176
surface_tool->set_bones(Vector<int>(bones));

editor/scene/3d/gizmos/two_bone_ik_3d_gizmo_plugin.cpp

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,35 +134,28 @@ Ref<ArrayMesh> TwoBoneIK3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, T
134134

135135
int root_bone = p_ik->get_root_bone(i);
136136
int middle_bone = p_ik->get_middle_bone(i);
137-
int end_bone = p_ik->get_end_bone(i);
138-
139-
bool is_extended = p_ik->is_end_bone_extended(i) && p_ik->get_end_bone_length(i) > 0.0f;
140137

141138
Transform3D root_gp = p_skeleton->get_bone_global_rest(root_bone);
142139
Transform3D mid_gp = p_skeleton->get_bone_global_rest(middle_bone);
143-
Transform3D end_gp = p_skeleton->get_bone_global_rest(end_bone);
144-
145-
Vector3 end_point = end_gp.origin;
146-
if (is_extended) {
147-
end_point += end_gp.basis.get_rotation_quaternion().xform(p_ik->get_bone_axis(end_bone, p_ik->get_end_bone_direction(i))).normalized() * p_ik->get_end_bone_length(i);
148-
}
140+
Vector3 root_vec = p_ik->get_root_bone_vector(i);
141+
Vector3 mid_vec = p_ik->get_middle_bone_vector(i);
149142

150143
bones.write[0] = root_bone;
151144
surface_tool->set_bones(bones);
152145
surface_tool->set_weights(weights);
153-
draw_line(surface_tool, root_gp.origin, mid_gp.origin, bone_color);
146+
draw_line(surface_tool, root_gp.origin, root_gp.translated_local(root_vec).origin, bone_color);
154147

155148
bones.write[0] = middle_bone;
156149
surface_tool->set_bones(bones);
157150
surface_tool->set_weights(weights);
158-
draw_line(surface_tool, mid_gp.origin, end_point, bone_color);
151+
draw_line(surface_tool, mid_gp.origin, mid_gp.translated_local(mid_vec).origin, bone_color);
159152

160153
Vector3 pole_vector = p_ik->get_pole_direction_vector(i);
161154
if (pole_vector.is_zero_approx()) {
162155
continue;
163156
}
164157

165-
float pole_length = MIN(root_gp.origin.distance_to(mid_gp.origin), mid_gp.origin.distance_to(end_point)) * 0.25;
158+
float pole_length = MIN(root_vec.length(), mid_vec.length()) * 0.25;
166159
draw_arrow(surface_tool, mid_gp.origin, mid_gp.basis.get_rotation_quaternion().xform(pole_vector).normalized(), pole_length, bone_color);
167160
}
168161

scene/3d/bone_constraint_3d.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ void BoneConstraint3D::set_apply_bone(int p_index, int p_bone) {
191191
Skeleton3D *sk = get_skeleton();
192192
if (sk) {
193193
if (settings[p_index]->apply_bone <= -1 || settings[p_index]->apply_bone >= sk->get_bone_count()) {
194-
WARN_PRINT("apply bone index out of range!");
194+
WARN_PRINT("Apply bone index out of range!");
195195
settings[p_index]->apply_bone = -1;
196196
} else {
197197
settings[p_index]->apply_bone_name = sk->get_bone_name(settings[p_index]->apply_bone);

scene/3d/chain_ik_3d.cpp

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ void ChainIK3D::set_extend_end_bone(int p_index, bool p_enabled) {
258258
}
259259
notify_property_list_changed();
260260
#ifdef TOOLS_ENABLED
261-
update_gizmos();
261+
_make_gizmo_dirty();
262262
#endif // TOOLS_ENABLED
263263
}
264264

@@ -270,14 +270,17 @@ bool ChainIK3D::is_end_bone_extended(int p_index) const {
270270
void ChainIK3D::set_end_bone_direction(int p_index, BoneDirection p_bone_direction) {
271271
ERR_FAIL_INDEX(p_index, (int)settings.size());
272272
chain_settings[p_index]->end_bone_direction = p_bone_direction;
273-
_make_simulation_dirty(p_index);
274273
Skeleton3D *sk = get_skeleton();
275274
if (sk && !chain_settings[p_index]->joints.is_empty()) {
276275
_validate_axis(sk, p_index, chain_settings[p_index]->joints.size() - 1);
277276
}
278277
#ifdef TOOLS_ENABLED
279-
update_gizmos();
278+
_make_gizmo_dirty();
280279
#endif // TOOLS_ENABLED
280+
if (mutable_bone_axes) {
281+
return; // Chain dir will be recaluclated in _update_bone_axis().
282+
}
283+
_make_simulation_dirty(p_index);
281284
}
282285

283286
SkeletonModifier3D::BoneDirection ChainIK3D::get_end_bone_direction(int p_index) const {
@@ -287,11 +290,15 @@ SkeletonModifier3D::BoneDirection ChainIK3D::get_end_bone_direction(int p_index)
287290

288291
void ChainIK3D::set_end_bone_length(int p_index, float p_length) {
289292
ERR_FAIL_INDEX(p_index, (int)settings.size());
293+
float old = chain_settings[p_index]->end_bone_length;
290294
chain_settings[p_index]->end_bone_length = p_length;
291-
_make_simulation_dirty(p_index);
292295
#ifdef TOOLS_ENABLED
293-
update_gizmos();
296+
_make_gizmo_dirty();
294297
#endif // TOOLS_ENABLED
298+
if (mutable_bone_axes && Math::is_zero_approx(old) == Math::is_zero_approx(p_length)) {
299+
return; // If chain size is not changed, length will be recaluclated in _update_bone_axis().
300+
}
301+
_make_simulation_dirty(p_index);
295302
}
296303

297304
float ChainIK3D::get_end_bone_length(int p_index) const {
@@ -472,14 +479,67 @@ void ChainIK3D::_update_joints(int p_index) {
472479
}
473480

474481
#ifdef TOOLS_ENABLED
475-
update_gizmos();
482+
_make_gizmo_dirty();
476483
#endif // TOOLS_ENABLED
477484
}
478485

479486
void ChainIK3D::_process_ik(Skeleton3D *p_skeleton, double p_delta) {
480487
//
481488
}
482489

490+
#ifdef TOOLS_ENABLED
491+
void ChainIK3D::_update_mutable_info() {
492+
if (!is_inside_tree()) {
493+
return;
494+
}
495+
Skeleton3D *skeleton = get_skeleton();
496+
if (!skeleton) {
497+
for (uint32_t i = 0; i < settings.size(); i++) {
498+
chain_settings[i]->root_global_rest = Transform3D();
499+
}
500+
}
501+
bool changed = false;
502+
for (uint32_t i = 0; i < settings.size(); i++) {
503+
int root_bone = chain_settings[i]->root_bone.bone;
504+
if (root_bone < 0) {
505+
continue;
506+
}
507+
Transform3D new_tr = get_bone_global_rest_mutable(skeleton, root_bone);
508+
changed = changed || !chain_settings[i]->root_global_rest.is_equal_approx(new_tr);
509+
chain_settings[i]->root_global_rest = new_tr;
510+
}
511+
if (changed) {
512+
_make_gizmo_dirty();
513+
}
514+
}
515+
516+
Transform3D ChainIK3D::get_bone_global_rest_mutable(Skeleton3D *p_skeleton, int p_bone) {
517+
int current = p_bone;
518+
Transform3D accum;
519+
int parent = p_skeleton->get_bone_parent(current);
520+
if (parent >= 0) {
521+
accum = p_skeleton->get_bone_global_rest(parent);
522+
}
523+
Transform3D tr = p_skeleton->get_bone_rest(current);
524+
// Note:
525+
// Chain IK gizmo might not be able to retrieve this pose in SkeletonModifier update process.
526+
// So the gizmo uses bone_vector insteads but parent of root bone doesn't have bone_vector.
527+
// Then, we needs to cache this pose in IK node.
528+
tr.origin = p_skeleton->get_bone_pose_position(current);
529+
accum *= tr;
530+
return accum;
531+
}
532+
533+
Transform3D ChainIK3D::get_chain_root_global_rest(int p_index) {
534+
ERR_FAIL_INDEX_V(p_index, (int)settings.size(), Transform3D());
535+
return chain_settings[p_index]->root_global_rest;
536+
}
537+
538+
Vector3 ChainIK3D::get_bone_vector(int p_index, int p_joint) const {
539+
return Vector3();
540+
}
541+
#endif // TOOLS_ENABLED
542+
483543
ChainIK3D::~ChainIK3D() {
484544
clear_settings();
485545
}

0 commit comments

Comments
 (0)