1/**************************************************************************/
2/* skeleton_3d_editor_plugin.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "skeleton_3d_editor_plugin.h"
32
33#include "core/io/resource_saver.h"
34#include "editor/editor_node.h"
35#include "editor/editor_properties.h"
36#include "editor/editor_properties_vector.h"
37#include "editor/editor_scale.h"
38#include "editor/editor_settings.h"
39#include "editor/editor_string_names.h"
40#include "editor/editor_undo_redo_manager.h"
41#include "editor/plugins/animation_player_editor_plugin.h"
42#include "node_3d_editor_plugin.h"
43#include "scene/3d/collision_shape_3d.h"
44#include "scene/3d/joint_3d.h"
45#include "scene/3d/mesh_instance_3d.h"
46#include "scene/3d/physics_body_3d.h"
47#include "scene/gui/separator.h"
48#include "scene/gui/texture_rect.h"
49#include "scene/resources/capsule_shape_3d.h"
50#include "scene/resources/skeleton_profile.h"
51#include "scene/resources/sphere_shape_3d.h"
52#include "scene/resources/surface_tool.h"
53#include "scene/scene_string_names.h"
54
55void BoneTransformEditor::create_editors() {
56 section = memnew(EditorInspectorSection);
57 section->setup("trf_properties", label, this, Color(0.0f, 0.0f, 0.0f), true);
58 section->unfold();
59 add_child(section);
60
61 enabled_checkbox = memnew(EditorPropertyCheck());
62 enabled_checkbox->set_label("Pose Enabled");
63 enabled_checkbox->set_selectable(false);
64 enabled_checkbox->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
65 section->get_vbox()->add_child(enabled_checkbox);
66
67 // Position property.
68 position_property = memnew(EditorPropertyVector3());
69 position_property->setup(-10000, 10000, 0.001f, true);
70 position_property->set_label("Position");
71 position_property->set_selectable(false);
72 position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
73 position_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
74 section->get_vbox()->add_child(position_property);
75
76 // Rotation property.
77 rotation_property = memnew(EditorPropertyQuaternion());
78 rotation_property->setup(-10000, 10000, 0.001f, true);
79 rotation_property->set_label("Rotation");
80 rotation_property->set_selectable(false);
81 rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
82 rotation_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
83 section->get_vbox()->add_child(rotation_property);
84
85 // Scale property.
86 scale_property = memnew(EditorPropertyVector3());
87 scale_property->setup(-10000, 10000, 0.001f, true);
88 scale_property->set_label("Scale");
89 scale_property->set_selectable(false);
90 scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
91 scale_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
92 section->get_vbox()->add_child(scale_property);
93
94 // Transform/Matrix section.
95 rest_section = memnew(EditorInspectorSection);
96 rest_section->setup("trf_properties_transform", "Rest", this, Color(0.0f, 0.0f, 0.0f), true);
97 section->get_vbox()->add_child(rest_section);
98
99 // Transform/Matrix property.
100 rest_matrix = memnew(EditorPropertyTransform3D());
101 rest_matrix->setup(-10000, 10000, 0.001f, true);
102 rest_matrix->set_label("Transform");
103 rest_matrix->set_selectable(false);
104 rest_section->get_vbox()->add_child(rest_matrix);
105}
106
107void BoneTransformEditor::_notification(int p_what) {
108 switch (p_what) {
109 case NOTIFICATION_THEME_CHANGED: {
110 const Color section_color = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
111 section->set_bg_color(section_color);
112 rest_section->set_bg_color(section_color);
113 } break;
114 }
115}
116
117void BoneTransformEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
118 if (updating) {
119 return;
120 }
121 if (skeleton) {
122 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
123 undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
124 undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property));
125 undo_redo->add_do_property(skeleton, p_property, p_value);
126 undo_redo->commit_action();
127 }
128}
129
130BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) :
131 skeleton(p_skeleton) {
132 create_editors();
133}
134
135void BoneTransformEditor::set_keyable(const bool p_keyable) {
136 position_property->set_keying(p_keyable);
137 rotation_property->set_keying(p_keyable);
138 scale_property->set_keying(p_keyable);
139}
140
141void BoneTransformEditor::set_target(const String &p_prop) {
142 enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled");
143 enabled_checkbox->update_property();
144
145 position_property->set_object_and_property(skeleton, p_prop + "position");
146 position_property->update_property();
147
148 rotation_property->set_object_and_property(skeleton, p_prop + "rotation");
149 rotation_property->update_property();
150
151 scale_property->set_object_and_property(skeleton, p_prop + "scale");
152 scale_property->update_property();
153
154 rest_matrix->set_object_and_property(skeleton, p_prop + "rest");
155 rest_matrix->update_property();
156}
157
158void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) {
159 AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
160 if (!te || !te->has_keying()) {
161 return;
162 }
163 te->_clear_selection();
164 PackedStringArray split = p_path.split("/");
165 if (split.size() == 3 && split[0] == "bones") {
166 int bone_idx = split[1].to_int();
167 if (split[2] == "position") {
168 te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, (Vector3)skeleton->get(p_path) / skeleton->get_motion_scale());
169 }
170 if (split[2] == "rotation") {
171 te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_ROTATION_3D, skeleton->get(p_path));
172 }
173 if (split[2] == "scale") {
174 te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_SCALE_3D, skeleton->get(p_path));
175 }
176 }
177}
178
179void BoneTransformEditor::_update_properties() {
180 if (!skeleton) {
181 return;
182 }
183 int selected = Skeleton3DEditor::get_singleton()->get_selected_bone();
184 List<PropertyInfo> props;
185 skeleton->get_property_list(&props);
186 for (const PropertyInfo &E : props) {
187 PackedStringArray split = E.name.split("/");
188 if (split.size() == 3 && split[0] == "bones") {
189 if (split[1].to_int() == selected) {
190 if (split[2] == "enabled") {
191 enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
192 enabled_checkbox->update_property();
193 enabled_checkbox->queue_redraw();
194 }
195 if (split[2] == "position") {
196 position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
197 position_property->update_property();
198 position_property->queue_redraw();
199 }
200 if (split[2] == "rotation") {
201 rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
202 rotation_property->update_property();
203 rotation_property->queue_redraw();
204 }
205 if (split[2] == "scale") {
206 scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
207 scale_property->update_property();
208 scale_property->queue_redraw();
209 }
210 if (split[2] == "rest") {
211 rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
212 rest_matrix->update_property();
213 rest_matrix->queue_redraw();
214 }
215 }
216 }
217 }
218}
219
220Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr;
221
222void Skeleton3DEditor::set_keyable(const bool p_keyable) {
223 keyable = p_keyable;
224 if (p_keyable) {
225 animation_hb->show();
226 } else {
227 animation_hb->hide();
228 }
229};
230
231void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enabled) {
232 skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_RESET_SELECTED_POSES, !p_bone_options_enabled);
233 skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled);
234};
235
236void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
237 if (!skeleton) {
238 return;
239 }
240
241 switch (p_skeleton_option) {
242 case SKELETON_OPTION_RESET_ALL_POSES: {
243 reset_pose(true);
244 break;
245 }
246 case SKELETON_OPTION_RESET_SELECTED_POSES: {
247 reset_pose(false);
248 break;
249 }
250 case SKELETON_OPTION_ALL_POSES_TO_RESTS: {
251 pose_to_rest(true);
252 break;
253 }
254 case SKELETON_OPTION_SELECTED_POSES_TO_RESTS: {
255 pose_to_rest(false);
256 break;
257 }
258 case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: {
259 create_physical_skeleton();
260 break;
261 }
262 case SKELETON_OPTION_EXPORT_SKELETON_PROFILE: {
263 export_skeleton_profile();
264 break;
265 }
266 }
267}
268
269void Skeleton3DEditor::reset_pose(const bool p_all_bones) {
270 if (!skeleton) {
271 return;
272 }
273 const int bone_count = skeleton->get_bone_count();
274 if (!bone_count) {
275 return;
276 }
277
278 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
279 ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
280 if (p_all_bones) {
281 for (int i = 0; i < bone_count; i++) {
282 ur->add_undo_method(skeleton, "set_bone_pose_position", i, skeleton->get_bone_pose_position(i));
283 ur->add_undo_method(skeleton, "set_bone_pose_rotation", i, skeleton->get_bone_pose_rotation(i));
284 ur->add_undo_method(skeleton, "set_bone_pose_scale", i, skeleton->get_bone_pose_scale(i));
285 }
286 ur->add_do_method(skeleton, "reset_bone_poses");
287 } else {
288 // Todo: Do method with multiple bone selection.
289 if (selected_bone == -1) {
290 ur->commit_action();
291 return;
292 }
293 ur->add_undo_method(skeleton, "set_bone_pose_position", selected_bone, skeleton->get_bone_pose_position(selected_bone));
294 ur->add_undo_method(skeleton, "set_bone_pose_rotation", selected_bone, skeleton->get_bone_pose_rotation(selected_bone));
295 ur->add_undo_method(skeleton, "set_bone_pose_scale", selected_bone, skeleton->get_bone_pose_scale(selected_bone));
296 ur->add_do_method(skeleton, "reset_bone_pose", selected_bone);
297 }
298 ur->commit_action();
299}
300
301void Skeleton3DEditor::insert_keys(const bool p_all_bones) {
302 if (!skeleton) {
303 return;
304 }
305
306 bool pos_enabled = key_loc_button->is_pressed();
307 bool rot_enabled = key_rot_button->is_pressed();
308 bool scl_enabled = key_scale_button->is_pressed();
309
310 int bone_len = skeleton->get_bone_count();
311 Node *root = EditorNode::get_singleton()->get_tree()->get_root();
312 String path = root->get_path_to(skeleton);
313
314 AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
315 te->make_insert_queue();
316 for (int i = 0; i < bone_len; i++) {
317 const String name = skeleton->get_bone_name(i);
318
319 if (name.is_empty()) {
320 continue;
321 }
322
323 if (pos_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_POSITION_3D))) {
324 te->insert_transform_key(skeleton, name, Animation::TYPE_POSITION_3D, skeleton->get_bone_pose_position(i) / skeleton->get_motion_scale());
325 }
326 if (rot_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_ROTATION_3D))) {
327 te->insert_transform_key(skeleton, name, Animation::TYPE_ROTATION_3D, skeleton->get_bone_pose_rotation(i));
328 }
329 if (scl_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_SCALE_3D))) {
330 te->insert_transform_key(skeleton, name, Animation::TYPE_SCALE_3D, skeleton->get_bone_pose_scale(i));
331 }
332 }
333 te->commit_insert_queue();
334}
335
336void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) {
337 if (!skeleton) {
338 return;
339 }
340 const int bone_count = skeleton->get_bone_count();
341 if (!bone_count) {
342 return;
343 }
344
345 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
346 ur->create_action(TTR("Set Bone Rest"), UndoRedo::MERGE_ENDS);
347 if (p_all_bones) {
348 for (int i = 0; i < bone_count; i++) {
349 ur->add_do_method(skeleton, "set_bone_rest", i, skeleton->get_bone_pose(i));
350 ur->add_undo_method(skeleton, "set_bone_rest", i, skeleton->get_bone_rest(i));
351 }
352 } else {
353 // Todo: Do method with multiple bone selection.
354 if (selected_bone == -1) {
355 ur->commit_action();
356 return;
357 }
358 ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone));
359 ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone));
360 }
361 ur->commit_action();
362}
363
364void Skeleton3DEditor::create_physical_skeleton() {
365 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
366 ERR_FAIL_COND(!get_tree());
367 Node *owner = get_tree()->get_edited_scene_root();
368
369 const int bone_count = skeleton->get_bone_count();
370
371 if (!bone_count) {
372 EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot create a physical skeleton for a Skeleton3D node with no bones.")));
373 return;
374 }
375
376 Vector<BoneInfo> bones_infos;
377 bones_infos.resize(bone_count);
378
379 ur->create_action(TTR("Create physical bones"), UndoRedo::MERGE_ALL);
380 for (int bone_id = 0; bone_count > bone_id; ++bone_id) {
381 const int parent = skeleton->get_bone_parent(bone_id);
382
383 if (parent < 0) {
384 bones_infos.write[bone_id].relative_rest = skeleton->get_bone_rest(bone_id);
385 } else {
386 const int parent_parent = skeleton->get_bone_parent(parent);
387
388 bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id);
389
390 // Create physical bone on parent.
391 if (!bones_infos[parent].physical_bone) {
392 PhysicalBone3D *physical_bone = create_physical_bone(parent, bone_id, bones_infos);
393 if (physical_bone && physical_bone->get_child(0)) {
394 CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(physical_bone->get_child(0));
395 if (collision_shape) {
396 bones_infos.write[parent].physical_bone = physical_bone;
397
398 ur->add_do_method(skeleton, "add_child", physical_bone);
399 ur->add_do_method(physical_bone, "set_owner", owner);
400 ur->add_do_method(collision_shape, "set_owner", owner);
401 ur->add_do_property(physical_bone, "bone_name", skeleton->get_bone_name(parent));
402
403 // Create joint between parent of parent.
404 if (parent_parent != -1) {
405 ur->add_do_method(physical_bone, "set_joint_type", PhysicalBone3D::JOINT_TYPE_PIN);
406 }
407
408 ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, physical_bone);
409 ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, collision_shape);
410
411 ur->add_do_reference(physical_bone);
412 ur->add_undo_method(skeleton, "remove_child", physical_bone);
413 }
414 }
415 }
416 }
417 }
418 ur->commit_action();
419}
420
421PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos) {
422 const Transform3D child_rest = skeleton->get_bone_rest(bone_child_id);
423
424 const real_t half_height(child_rest.origin.length() * 0.5);
425 const real_t radius(half_height * 0.2);
426
427 CapsuleShape3D *bone_shape_capsule = memnew(CapsuleShape3D);
428 bone_shape_capsule->set_height(half_height * 2);
429 bone_shape_capsule->set_radius(radius);
430
431 CollisionShape3D *bone_shape = memnew(CollisionShape3D);
432 bone_shape->set_shape(bone_shape_capsule);
433 bone_shape->set_name("CollisionShape3D");
434
435 Transform3D capsule_transform;
436 capsule_transform.basis.rows[0] = Vector3(1, 0, 0);
437 capsule_transform.basis.rows[1] = Vector3(0, 0, 1);
438 capsule_transform.basis.rows[2] = Vector3(0, -1, 0);
439 bone_shape->set_transform(capsule_transform);
440
441 /// Get an up vector not collinear with child rest origin
442 Vector3 up = Vector3(0, 1, 0);
443 if (up.cross(child_rest.origin).is_zero_approx()) {
444 up = Vector3(0, 0, 1);
445 }
446
447 Transform3D body_transform;
448 body_transform.basis = Basis::looking_at(child_rest.origin, up);
449 body_transform.origin = body_transform.basis.xform(Vector3(0, 0, -half_height));
450
451 Transform3D joint_transform;
452 joint_transform.origin = Vector3(0, 0, half_height);
453
454 PhysicalBone3D *physical_bone = memnew(PhysicalBone3D);
455 physical_bone->add_child(bone_shape);
456 physical_bone->set_name("Physical Bone " + skeleton->get_bone_name(bone_id));
457 physical_bone->set_body_offset(body_transform);
458 physical_bone->set_joint_offset(joint_transform);
459 return physical_bone;
460}
461
462void Skeleton3DEditor::export_skeleton_profile() {
463 if (!skeleton->get_bone_count()) {
464 EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot export a SkeletonProfile for a Skeleton3D node with no bones.")));
465 return;
466 }
467
468 file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
469 file_dialog->set_title(TTR("Export Skeleton Profile As..."));
470
471 List<String> exts;
472 ResourceLoader::get_recognized_extensions_for_type("SkeletonProfile", &exts);
473 file_dialog->clear_filters();
474 for (const String &K : exts) {
475 file_dialog->add_filter("*." + K);
476 }
477
478 file_dialog->popup_file_dialog();
479}
480
481void Skeleton3DEditor::_file_selected(const String &p_file) {
482 // Export SkeletonProfile.
483 Ref<SkeletonProfile> sp(memnew(SkeletonProfile));
484
485 // Build SkeletonProfile.
486 sp->set_group_size(1);
487
488 Vector<Vector2> handle_positions;
489 Vector2 position_max;
490 Vector2 position_min;
491
492 const int bone_count = skeleton->get_bone_count();
493 sp->set_bone_size(bone_count);
494 for (int i = 0; i < bone_count; i++) {
495 sp->set_bone_name(i, skeleton->get_bone_name(i));
496 int parent = skeleton->get_bone_parent(i);
497 if (parent >= 0) {
498 sp->set_bone_parent(i, skeleton->get_bone_name(parent));
499 }
500 sp->set_reference_pose(i, skeleton->get_bone_rest(i));
501
502 Transform3D grest = skeleton->get_bone_global_rest(i);
503 handle_positions.append(Vector2(grest.origin.x, grest.origin.y));
504 if (i == 0) {
505 position_max = Vector2(grest.origin.x, grest.origin.y);
506 position_min = Vector2(grest.origin.x, grest.origin.y);
507 } else {
508 position_max.x = MAX(grest.origin.x, position_max.x);
509 position_max.y = MAX(grest.origin.y, position_max.y);
510 position_min.x = MIN(grest.origin.x, position_min.x);
511 position_min.y = MIN(grest.origin.y, position_min.y);
512 }
513 }
514
515 // Layout handles provisionaly.
516 Vector2 bound = Vector2(position_max.x - position_min.x, position_max.y - position_min.y);
517 Vector2 center = Vector2((position_max.x + position_min.x) * 0.5, (position_max.y + position_min.y) * 0.5);
518 float nrm = MAX(bound.x, bound.y);
519 if (nrm > 0) {
520 for (int i = 0; i < bone_count; i++) {
521 handle_positions.write[i] = (handle_positions[i] - center) / nrm * 0.9;
522 sp->set_handle_offset(i, Vector2(0.5 + handle_positions[i].x, 0.5 - handle_positions[i].y));
523 }
524 }
525
526 Error err = ResourceSaver::save(sp, p_file);
527
528 if (err != OK) {
529 EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s"), p_file));
530 return;
531 }
532}
533
534Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
535 TreeItem *selected = joint_tree->get_selected();
536
537 if (!selected) {
538 return Variant();
539 }
540
541 Ref<Texture> icon = selected->get_icon(0);
542
543 VBoxContainer *vb = memnew(VBoxContainer);
544 HBoxContainer *hb = memnew(HBoxContainer);
545 TextureRect *tf = memnew(TextureRect);
546 tf->set_texture(icon);
547 tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
548 hb->add_child(tf);
549 Label *label = memnew(Label(selected->get_text(0)));
550 hb->add_child(label);
551 vb->add_child(hb);
552 hb->set_modulate(Color(1, 1, 1, 1));
553
554 set_drag_preview(vb);
555 Dictionary drag_data;
556 drag_data["type"] = "nodes";
557 drag_data["node"] = selected;
558
559 return drag_data;
560}
561
562bool Skeleton3DEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
563 TreeItem *target = joint_tree->get_item_at_position(p_point);
564 if (!target) {
565 return false;
566 }
567
568 const String path = target->get_metadata(0);
569 if (!path.begins_with("bones/")) {
570 return false;
571 }
572
573 TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node"]);
574 if (target == selected) {
575 return false;
576 }
577
578 const String path2 = target->get_metadata(0);
579 if (!path2.begins_with("bones/")) {
580 return false;
581 }
582
583 return true;
584}
585
586void Skeleton3DEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
587 if (!can_drop_data_fw(p_point, p_data, p_from)) {
588 return;
589 }
590
591 TreeItem *target = joint_tree->get_item_at_position(p_point);
592 TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node"]);
593
594 const BoneId target_boneidx = String(target->get_metadata(0)).get_slicec('/', 1).to_int();
595 const BoneId selected_boneidx = String(selected->get_metadata(0)).get_slicec('/', 1).to_int();
596
597 move_skeleton_bone(skeleton->get_path(), selected_boneidx, target_boneidx);
598}
599
600void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx) {
601 Node *node = get_node_or_null(p_skeleton_path);
602 Skeleton3D *skeleton_node = Object::cast_to<Skeleton3D>(node);
603 ERR_FAIL_NULL(skeleton_node);
604 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
605 ur->create_action(TTR("Set Bone Parentage"));
606 // If the target is a child of ourselves, we move only *us* and not our children.
607 if (skeleton_node->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) {
608 const BoneId parent_idx = skeleton_node->get_bone_parent(p_selected_boneidx);
609 const int bone_count = skeleton_node->get_bone_count();
610 for (BoneId i = 0; i < bone_count; ++i) {
611 if (skeleton_node->get_bone_parent(i) == p_selected_boneidx) {
612 ur->add_undo_method(skeleton_node, "set_bone_parent", i, skeleton_node->get_bone_parent(i));
613 ur->add_do_method(skeleton_node, "set_bone_parent", i, parent_idx);
614 skeleton_node->set_bone_parent(i, parent_idx);
615 }
616 }
617 }
618 ur->add_undo_method(skeleton_node, "set_bone_parent", p_selected_boneidx, skeleton_node->get_bone_parent(p_selected_boneidx));
619 ur->add_do_method(skeleton_node, "set_bone_parent", p_selected_boneidx, p_target_boneidx);
620 skeleton_node->set_bone_parent(p_selected_boneidx, p_target_boneidx);
621
622 update_joint_tree();
623 ur->commit_action();
624}
625
626void Skeleton3DEditor::_joint_tree_selection_changed() {
627 TreeItem *selected = joint_tree->get_selected();
628 if (selected) {
629 const String path = selected->get_metadata(0);
630 if (!path.begins_with("bones/")) {
631 return;
632 }
633 const int b_idx = path.get_slicec('/', 1).to_int();
634 selected_bone = b_idx;
635 if (pose_editor) {
636 const String bone_path = "bones/" + itos(b_idx) + "/";
637 pose_editor->set_target(bone_path);
638 pose_editor->set_keyable(keyable);
639 }
640 }
641
642 if (pose_editor && pose_editor->is_inside_tree()) {
643 pose_editor->set_visible(selected);
644 }
645 set_bone_options_enabled(selected);
646
647 _update_properties();
648 _update_gizmo_visible();
649}
650
651// May be not used with single select mode.
652void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) {
653}
654
655void Skeleton3DEditor::_update_properties() {
656 if (pose_editor) {
657 pose_editor->_update_properties();
658 }
659 Node3DEditor::get_singleton()->update_transform_gizmo();
660}
661
662void Skeleton3DEditor::update_joint_tree() {
663 joint_tree->clear();
664
665 if (!skeleton) {
666 return;
667 }
668
669 TreeItem *root = joint_tree->create_item();
670
671 HashMap<int, TreeItem *> items;
672
673 items.insert(-1, root);
674
675 Ref<Texture> bone_icon = get_editor_theme_icon(SNAME("BoneAttachment3D"));
676
677 Vector<int> bones_to_process = skeleton->get_parentless_bones();
678 while (bones_to_process.size() > 0) {
679 int current_bone_idx = bones_to_process[0];
680 bones_to_process.erase(current_bone_idx);
681
682 const int parent_idx = skeleton->get_bone_parent(current_bone_idx);
683 TreeItem *parent_item = items.find(parent_idx)->value;
684
685 TreeItem *joint_item = joint_tree->create_item(parent_item);
686 items.insert(current_bone_idx, joint_item);
687
688 joint_item->set_text(0, skeleton->get_bone_name(current_bone_idx));
689 joint_item->set_icon(0, bone_icon);
690 joint_item->set_selectable(0, true);
691 joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));
692
693 // Add the bone's children to the list of bones to be processed.
694 Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx);
695 int child_bone_size = current_bone_child_bones.size();
696 for (int i = 0; i < child_bone_size; i++) {
697 bones_to_process.push_back(current_bone_child_bones[i]);
698 }
699 }
700}
701
702void Skeleton3DEditor::create_editors() {
703 set_h_size_flags(SIZE_EXPAND_FILL);
704 set_focus_mode(FOCUS_ALL);
705
706 Node3DEditor *ne = Node3DEditor::get_singleton();
707 AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
708
709 // Create File dialog.
710 file_dialog = memnew(EditorFileDialog);
711 file_dialog->connect("file_selected", callable_mp(this, &Skeleton3DEditor::_file_selected));
712 add_child(file_dialog);
713
714 // Create Top Menu Bar.
715 HBoxContainer *topmenu_bar = memnew(HBoxContainer);
716 ne->add_control_to_menu_panel(topmenu_bar);
717
718 // Create Skeleton Option in Top Menu Bar.
719 skeleton_options = memnew(MenuButton);
720 topmenu_bar->add_child(skeleton_options);
721
722 skeleton_options->set_text(TTR("Skeleton3D"));
723
724 // Skeleton options.
725 PopupMenu *p = skeleton_options->get_popup();
726 p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/reset_all_poses", TTR("Reset All Bone Poses")), SKELETON_OPTION_RESET_ALL_POSES);
727 p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/reset_selected_poses", TTR("Reset Selected Poses")), SKELETON_OPTION_RESET_SELECTED_POSES);
728 p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/all_poses_to_rests", TTR("Apply All Poses to Rests")), SKELETON_OPTION_ALL_POSES_TO_RESTS);
729 p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/selected_poses_to_rests", TTR("Apply Selected Poses to Rests")), SKELETON_OPTION_SELECTED_POSES_TO_RESTS);
730 p->add_item(TTR("Create Physical Skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON);
731 p->add_item(TTR("Export Skeleton Profile"), SKELETON_OPTION_EXPORT_SKELETON_PROFILE);
732
733 p->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option));
734 set_bone_options_enabled(false);
735
736 Vector<Variant> button_binds;
737 button_binds.resize(1);
738
739 edit_mode_button = memnew(Button);
740 topmenu_bar->add_child(edit_mode_button);
741 edit_mode_button->set_flat(true);
742 edit_mode_button->set_toggle_mode(true);
743 edit_mode_button->set_focus_mode(FOCUS_NONE);
744 edit_mode_button->set_tooltip_text(TTR("Edit Mode\nShow buttons on joints."));
745 edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled));
746
747 edit_mode = false;
748
749 if (skeleton) {
750 skeleton->add_child(handles_mesh_instance);
751 handles_mesh_instance->set_skeleton_path(NodePath(""));
752 }
753
754 // Keying buttons.
755 animation_hb = memnew(HBoxContainer);
756 topmenu_bar->add_child(animation_hb);
757 animation_hb->add_child(memnew(VSeparator));
758 animation_hb->hide();
759
760 key_loc_button = memnew(Button);
761 key_loc_button->set_flat(true);
762 key_loc_button->set_toggle_mode(true);
763 key_loc_button->set_pressed(false);
764 key_loc_button->set_focus_mode(FOCUS_NONE);
765 key_loc_button->set_tooltip_text(TTR("Translation mask for inserting keys."));
766 animation_hb->add_child(key_loc_button);
767
768 key_rot_button = memnew(Button);
769 key_rot_button->set_flat(true);
770 key_rot_button->set_toggle_mode(true);
771 key_rot_button->set_pressed(true);
772 key_rot_button->set_focus_mode(FOCUS_NONE);
773 key_rot_button->set_tooltip_text(TTR("Rotation mask for inserting keys."));
774 animation_hb->add_child(key_rot_button);
775
776 key_scale_button = memnew(Button);
777 key_scale_button->set_flat(true);
778 key_scale_button->set_toggle_mode(true);
779 key_scale_button->set_pressed(false);
780 key_scale_button->set_focus_mode(FOCUS_NONE);
781 key_scale_button->set_tooltip_text(TTR("Scale mask for inserting keys."));
782 animation_hb->add_child(key_scale_button);
783
784 key_insert_button = memnew(Button);
785 key_insert_button->set_flat(true);
786 key_insert_button->set_focus_mode(FOCUS_NONE);
787 key_insert_button->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys).bind(false));
788 key_insert_button->set_tooltip_text(TTR("Insert key of bone poses already exist track."));
789 key_insert_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_to_existing_tracks", TTR("Insert Key (Existing Tracks)"), Key::INSERT));
790 animation_hb->add_child(key_insert_button);
791
792 key_insert_all_button = memnew(Button);
793 key_insert_all_button->set_flat(true);
794 key_insert_all_button->set_focus_mode(FOCUS_NONE);
795 key_insert_all_button->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys).bind(true));
796 key_insert_all_button->set_tooltip_text(TTR("Insert key of all bone poses."));
797 key_insert_all_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_of_all_bones", TTR("Insert Key (All Bones)"), KeyModifierMask::CMD_OR_CTRL + Key::INSERT));
798 animation_hb->add_child(key_insert_all_button);
799
800 // Bone tree.
801 bones_section = memnew(EditorInspectorSection);
802 bones_section->setup("bones", "Bones", skeleton, Color(0.0f, 0.0, 0.0f), true);
803 add_child(bones_section);
804 bones_section->unfold();
805
806 ScrollContainer *s_con = memnew(ScrollContainer);
807 s_con->set_h_size_flags(SIZE_EXPAND_FILL);
808 s_con->set_custom_minimum_size(Size2(1, 350) * EDSCALE);
809 bones_section->get_vbox()->add_child(s_con);
810
811 joint_tree = memnew(Tree);
812 joint_tree->set_columns(1);
813 joint_tree->set_focus_mode(Control::FOCUS_NONE);
814 joint_tree->set_select_mode(Tree::SELECT_SINGLE);
815 joint_tree->set_hide_root(true);
816 joint_tree->set_v_size_flags(SIZE_EXPAND_FILL);
817 joint_tree->set_h_size_flags(SIZE_EXPAND_FILL);
818 joint_tree->set_allow_rmb_select(true);
819 SET_DRAG_FORWARDING_GCD(joint_tree, Skeleton3DEditor);
820 s_con->add_child(joint_tree);
821
822 pose_editor = memnew(BoneTransformEditor(skeleton));
823 pose_editor->set_label(TTR("Bone Transform"));
824 pose_editor->set_visible(false);
825 add_child(pose_editor);
826
827 set_keyable(te->has_keying());
828}
829
830void Skeleton3DEditor::_notification(int p_what) {
831 switch (p_what) {
832 case NOTIFICATION_ENTER_TREE: {
833 update_joint_tree();
834
835 joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed));
836 joint_tree->connect("item_mouse_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select));
837#ifdef TOOLS_ENABLED
838 skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
839 skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties));
840 skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed));
841 skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible));
842#endif
843
844 get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Object::CONNECT_ONE_SHOT);
845 } break;
846 case NOTIFICATION_READY: {
847 // Will trigger NOTIFICATION_THEME_CHANGED, but won't cause any loops if called here.
848 add_theme_constant_override("separation", 0);
849 } break;
850 case NOTIFICATION_THEME_CHANGED: {
851 skeleton_options->set_icon(get_editor_theme_icon(SNAME("Skeleton3D")));
852 edit_mode_button->set_icon(get_editor_theme_icon(SNAME("ToolBoneSelect")));
853 key_loc_button->set_icon(get_editor_theme_icon(SNAME("KeyPosition")));
854 key_rot_button->set_icon(get_editor_theme_icon(SNAME("KeyRotation")));
855 key_scale_button->set_icon(get_editor_theme_icon(SNAME("KeyScale")));
856 key_insert_button->set_icon(get_editor_theme_icon(SNAME("Key")));
857 key_insert_all_button->set_icon(get_editor_theme_icon(SNAME("NewKey")));
858 bones_section->set_bg_color(get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
859
860 update_joint_tree();
861 } break;
862 case NOTIFICATION_PREDELETE: {
863 if (skeleton) {
864 select_bone(-1); // Requires that the joint_tree has not been deleted.
865#ifdef TOOLS_ENABLED
866 skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible));
867 skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed));
868 skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
869 skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties));
870 skeleton->set_transform_gizmo_visible(true);
871#endif
872 handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance);
873 }
874 edit_mode_toggled(false);
875 } break;
876 }
877}
878
879void Skeleton3DEditor::_node_removed(Node *p_node) {
880 if (skeleton && p_node == skeleton) {
881 skeleton = nullptr;
882 skeleton_options->hide();
883 }
884
885 _update_properties();
886}
887
888void Skeleton3DEditor::_bind_methods() {
889 ClassDB::bind_method(D_METHOD("_node_removed"), &Skeleton3DEditor::_node_removed);
890 ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &Skeleton3DEditor::_joint_tree_selection_changed);
891 ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select);
892 ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties);
893 ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option);
894
895 ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone);
896
897 ClassDB::bind_method(D_METHOD("_draw_gizmo"), &Skeleton3DEditor::_draw_gizmo);
898}
899
900void Skeleton3DEditor::edit_mode_toggled(const bool pressed) {
901 edit_mode = pressed;
902 _update_gizmo_visible();
903}
904
905Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skeleton3D *p_skeleton) :
906 editor_plugin(e_plugin),
907 skeleton(p_skeleton) {
908 singleton = this;
909
910 // Handle.
911 handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial));
912 handle_shader = Ref<Shader>(memnew(Shader));
913 handle_shader->set_code(R"(
914// Skeleton 3D gizmo handle shader.
915
916shader_type spatial;
917render_mode unshaded, shadows_disabled, depth_draw_always;
918uniform sampler2D texture_albedo : source_color;
919uniform float point_size : hint_range(0,128) = 32;
920void vertex() {
921 if (!OUTPUT_IS_SRGB) {
922 COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) );
923 }
924 VERTEX = VERTEX;
925 POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0);
926 POSITION.z = mix(POSITION.z, 0, 0.999);
927 POINT_SIZE = point_size;
928}
929void fragment() {
930 vec4 albedo_tex = texture(texture_albedo,POINT_COORD);
931 vec3 col = albedo_tex.rgb + COLOR.rgb;
932 col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0));
933 ALBEDO = col;
934 if (albedo_tex.a < 0.5) { discard; }
935 ALPHA = albedo_tex.a;
936}
937)");
938 handle_material->set_shader(handle_shader);
939 Ref<Texture2D> handle = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorBoneHandle"), EditorStringName(EditorIcons));
940 handle_material->set_shader_parameter("point_size", handle->get_width());
941 handle_material->set_shader_parameter("texture_albedo", handle);
942
943 handles_mesh_instance = memnew(MeshInstance3D);
944 handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF);
945 handles_mesh.instantiate();
946 handles_mesh_instance->set_mesh(handles_mesh);
947
948 create_editors();
949}
950
951void Skeleton3DEditor::update_bone_original() {
952 if (!skeleton) {
953 return;
954 }
955 if (skeleton->get_bone_count() == 0 || selected_bone == -1) {
956 return;
957 }
958 bone_original_position = skeleton->get_bone_pose_position(selected_bone);
959 bone_original_rotation = skeleton->get_bone_pose_rotation(selected_bone);
960 bone_original_scale = skeleton->get_bone_pose_scale(selected_bone);
961}
962
963void Skeleton3DEditor::_hide_handles() {
964 handles_mesh_instance->hide();
965}
966
967void Skeleton3DEditor::_draw_gizmo() {
968 if (!skeleton) {
969 return;
970 }
971
972 // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode,
973 // the skeleton update will be done first and
974 // the drawing surface will be interrupted once and an error will occur.
975 skeleton->force_update_all_dirty_bones();
976
977 // Handles.
978 if (edit_mode) {
979 _draw_handles();
980 } else {
981 _hide_handles();
982 }
983}
984
985void Skeleton3DEditor::_draw_handles() {
986 const int bone_count = skeleton->get_bone_count();
987
988 handles_mesh->clear_surfaces();
989
990 if (bone_count) {
991 handles_mesh_instance->show();
992
993 handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS);
994
995 for (int i = 0; i < bone_count; i++) {
996 Color c;
997 if (i == selected_bone) {
998 c = Color(1, 1, 0);
999 } else {
1000 c = Color(0.1, 0.25, 0.8);
1001 }
1002 Vector3 point = skeleton->get_bone_global_pose(i).origin;
1003 handles_mesh->surface_set_color(c);
1004 handles_mesh->surface_add_vertex(point);
1005 }
1006 handles_mesh->surface_end();
1007 handles_mesh->surface_set_material(0, handle_material);
1008 } else {
1009 handles_mesh_instance->hide();
1010 }
1011}
1012
1013TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) {
1014 if (!p_node) {
1015 return nullptr;
1016 }
1017
1018 NodePath np = p_node->get_metadata(0);
1019 if (np == p_path) {
1020 return p_node;
1021 }
1022
1023 TreeItem *children = p_node->get_first_child();
1024 while (children) {
1025 TreeItem *n = _find(children, p_path);
1026 if (n) {
1027 return n;
1028 }
1029 children = children->get_next();
1030 }
1031
1032 return nullptr;
1033}
1034
1035void Skeleton3DEditor::_subgizmo_selection_change() {
1036 if (!skeleton) {
1037 return;
1038 }
1039
1040 // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree.
1041 if (!edit_mode) {
1042 skeleton->clear_subgizmo_selection();
1043 return;
1044 }
1045
1046 int selected = -1;
1047 Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
1048 if (se) {
1049 selected = se->get_selected_bone();
1050 }
1051
1052 if (selected >= 0) {
1053 Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos();
1054 for (int i = 0; i < gizmos.size(); i++) {
1055 Ref<EditorNode3DGizmo> gizmo = gizmos[i];
1056 if (!gizmo.is_valid()) {
1057 continue;
1058 }
1059 Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin();
1060 if (!plugin.is_valid()) {
1061 continue;
1062 }
1063 skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected));
1064 break;
1065 }
1066 } else {
1067 skeleton->clear_subgizmo_selection();
1068 }
1069}
1070
1071void Skeleton3DEditor::select_bone(int p_idx) {
1072 if (p_idx >= 0) {
1073 TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx));
1074 if (ti) {
1075 // Make visible when it's collapsed.
1076 TreeItem *node = ti->get_parent();
1077 while (node && node != joint_tree->get_root()) {
1078 node->set_collapsed(false);
1079 node = node->get_parent();
1080 }
1081 ti->select(0);
1082 joint_tree->scroll_to_item(ti);
1083 }
1084 } else {
1085 selected_bone = -1;
1086 joint_tree->deselect_all();
1087 _joint_tree_selection_changed();
1088 }
1089}
1090
1091Skeleton3DEditor::~Skeleton3DEditor() {
1092 singleton = nullptr;
1093
1094 handles_mesh_instance->queue_free();
1095
1096 Node3DEditor *ne = Node3DEditor::get_singleton();
1097
1098 if (animation_hb) {
1099 ne->remove_control_from_menu_panel(animation_hb);
1100 memdelete(animation_hb);
1101 }
1102
1103 if (separator) {
1104 ne->remove_control_from_menu_panel(separator);
1105 memdelete(separator);
1106 }
1107
1108 if (skeleton_options) {
1109 ne->remove_control_from_menu_panel(skeleton_options);
1110 memdelete(skeleton_options);
1111 }
1112
1113 if (edit_mode_button) {
1114 ne->remove_control_from_menu_panel(edit_mode_button);
1115 memdelete(edit_mode_button);
1116 }
1117}
1118
1119bool EditorInspectorPluginSkeleton::can_handle(Object *p_object) {
1120 return Object::cast_to<Skeleton3D>(p_object) != nullptr;
1121}
1122
1123void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) {
1124 Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object);
1125 ERR_FAIL_NULL(skeleton);
1126
1127 skel_editor = memnew(Skeleton3DEditor(this, skeleton));
1128 add_custom_control(skel_editor);
1129}
1130
1131Skeleton3DEditorPlugin::Skeleton3DEditorPlugin() {
1132 skeleton_plugin = memnew(EditorInspectorPluginSkeleton);
1133
1134 EditorInspector::add_inspector_plugin(skeleton_plugin);
1135
1136 Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin));
1137 Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
1138}
1139
1140EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
1141 Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
1142 Node3DEditor *ne = Node3DEditor::get_singleton();
1143 if (se && se->is_edit_mode()) {
1144 const Ref<InputEventMouseButton> mb = p_event;
1145 if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
1146 if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) {
1147 if (!ne->is_gizmo_visible()) {
1148 return EditorPlugin::AFTER_GUI_INPUT_STOP;
1149 }
1150 }
1151 if (mb->is_pressed()) {
1152 se->update_bone_original();
1153 }
1154 }
1155 return EditorPlugin::AFTER_GUI_INPUT_CUSTOM;
1156 }
1157 return EditorPlugin::AFTER_GUI_INPUT_PASS;
1158}
1159
1160bool Skeleton3DEditorPlugin::handles(Object *p_object) const {
1161 return p_object->is_class("Skeleton3D");
1162}
1163
1164void Skeleton3DEditor::_bone_enabled_changed(const int p_bone_id) {
1165 _update_gizmo_visible();
1166}
1167
1168void Skeleton3DEditor::_update_gizmo_visible() {
1169 _subgizmo_selection_change();
1170 if (edit_mode) {
1171 if (selected_bone == -1) {
1172#ifdef TOOLS_ENABLED
1173 skeleton->set_transform_gizmo_visible(false);
1174#endif
1175 } else {
1176#ifdef TOOLS_ENABLED
1177 if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) {
1178 skeleton->set_transform_gizmo_visible(true);
1179 } else {
1180 skeleton->set_transform_gizmo_visible(false);
1181 }
1182#endif
1183 }
1184 } else {
1185#ifdef TOOLS_ENABLED
1186 skeleton->set_transform_gizmo_visible(true);
1187#endif
1188 }
1189 _draw_gizmo();
1190}
1191
1192int Skeleton3DEditor::get_selected_bone() const {
1193 return selected_bone;
1194}
1195
1196Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
1197 unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
1198 unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
1199 unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
1200 unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
1201 unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
1202
1203 selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial));
1204 selected_sh = Ref<Shader>(memnew(Shader));
1205 selected_sh->set_code(R"(
1206// Skeleton 3D gizmo bones shader.
1207
1208shader_type spatial;
1209render_mode unshaded, shadows_disabled;
1210void vertex() {
1211 if (!OUTPUT_IS_SRGB) {
1212 COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) );
1213 }
1214 VERTEX = VERTEX;
1215 POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0);
1216 POSITION.z = mix(POSITION.z, 0, 0.998);
1217}
1218void fragment() {
1219 ALBEDO = COLOR.rgb;
1220 ALPHA = COLOR.a;
1221}
1222)");
1223 selected_mat->set_shader(selected_sh);
1224
1225 // Register properties in editor settings.
1226 EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
1227 EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0));
1228 EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1);
1229 EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape", 1);
1230 EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron"));
1231}
1232
1233bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
1234 return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
1235}
1236
1237String Skeleton3DGizmoPlugin::get_gizmo_name() const {
1238 return "Skeleton3D";
1239}
1240
1241int Skeleton3DGizmoPlugin::get_priority() const {
1242 return -1;
1243}
1244
1245int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
1246 Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d());
1247 ERR_FAIL_NULL_V(skeleton, -1);
1248
1249 Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
1250
1251 if (!se || !se->is_edit_mode()) {
1252 return -1;
1253 }
1254
1255 if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) {
1256 return -1;
1257 }
1258
1259 // Select bone.
1260 real_t grab_threshold = 4 * EDSCALE;
1261 Vector3 ray_from = p_camera->get_global_transform().origin;
1262 Transform3D gt = skeleton->get_global_transform();
1263 int closest_idx = -1;
1264 real_t closest_dist = 1e10;
1265 const int bone_count = skeleton->get_bone_count();
1266 for (int i = 0; i < bone_count; i++) {
1267 Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin);
1268 Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d);
1269 real_t dist_3d = ray_from.distance_to(joint_pos_3d);
1270 real_t dist_2d = p_point.distance_to(joint_pos_2d);
1271 if (dist_2d < grab_threshold && dist_3d < closest_dist) {
1272 closest_dist = dist_3d;
1273 closest_idx = i;
1274 }
1275 }
1276
1277 if (closest_idx >= 0) {
1278 se->select_bone(closest_idx);
1279 return closest_idx;
1280 }
1281
1282 se->select_bone(-1);
1283 return -1;
1284}
1285
1286Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const {
1287 Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d());
1288 ERR_FAIL_NULL_V(skeleton, Transform3D());
1289
1290 return skeleton->get_bone_global_pose(p_id);
1291}
1292
1293void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) {
1294 Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d());
1295 ERR_FAIL_NULL(skeleton);
1296
1297 // Prepare for global to local.
1298 Transform3D original_to_local;
1299 int parent_idx = skeleton->get_bone_parent(p_id);
1300 if (parent_idx >= 0) {
1301 original_to_local = skeleton->get_bone_global_pose(parent_idx);
1302 }
1303 Basis to_local = original_to_local.get_basis().inverse();
1304
1305 // Prepare transform.
1306 Transform3D t;
1307
1308 // Basis.
1309 t.basis = to_local * p_transform.get_basis();
1310
1311 // Origin.
1312 Vector3 orig = skeleton->get_bone_pose(p_id).origin;
1313 Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin;
1314 t.origin = orig + to_local.xform(sub);
1315
1316 // Apply transform.
1317 skeleton->set_bone_pose_position(p_id, t.origin);
1318 skeleton->set_bone_pose_rotation(p_id, t.basis.get_rotation_quaternion());
1319 skeleton->set_bone_pose_scale(p_id, t.basis.get_scale());
1320}
1321
1322void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) {
1323 Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d());
1324 ERR_FAIL_NULL(skeleton);
1325
1326 Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
1327 Node3DEditor *ne = Node3DEditor::get_singleton();
1328
1329 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1330 ur->create_action(TTR("Set Bone Transform"));
1331 if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) {
1332 for (int i = 0; i < p_ids.size(); i++) {
1333 ur->add_do_method(skeleton, "set_bone_pose_position", p_ids[i], skeleton->get_bone_pose_position(p_ids[i]));
1334 ur->add_undo_method(skeleton, "set_bone_pose_position", p_ids[i], se->get_bone_original_position());
1335 }
1336 }
1337 if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
1338 for (int i = 0; i < p_ids.size(); i++) {
1339 ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i]));
1340 ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation());
1341 }
1342 }
1343 if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) {
1344 for (int i = 0; i < p_ids.size(); i++) {
1345 // If the axis is swapped by scaling, the rotation can be changed.
1346 ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i]));
1347 ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation());
1348 ur->add_do_method(skeleton, "set_bone_pose_scale", p_ids[i], skeleton->get_bone_pose_scale(p_ids[i]));
1349 ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale());
1350 }
1351 }
1352 ur->commit_action();
1353}
1354
1355void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
1356 Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d());
1357 p_gizmo->clear();
1358
1359 if (!skeleton->get_bone_count()) {
1360 return;
1361 }
1362
1363 int selected = -1;
1364 Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
1365 if (se) {
1366 selected = se->get_selected_bone();
1367 }
1368
1369 Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/skeleton");
1370 Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone");
1371 real_t bone_axis_length = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_axis_length");
1372 int bone_shape = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_shape");
1373
1374 LocalVector<Color> axis_colors;
1375 axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));
1376 axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));
1377 axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor)));
1378
1379 Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
1380 surface_tool->begin(Mesh::PRIMITIVE_LINES);
1381
1382 if (p_gizmo->is_selected()) {
1383 surface_tool->set_material(selected_mat);
1384 } else {
1385 unselected_mat->set_albedo(bone_color);
1386 surface_tool->set_material(unselected_mat);
1387 }
1388
1389 LocalVector<int> bones;
1390 LocalVector<float> weights;
1391 bones.resize(4);
1392 weights.resize(4);
1393 for (int i = 0; i < 4; i++) {
1394 bones[i] = 0;
1395 weights[i] = 0;
1396 }
1397 weights[0] = 1;
1398
1399 int current_bone_index = 0;
1400 Vector<int> bones_to_process = skeleton->get_parentless_bones();
1401
1402 while (bones_to_process.size() > current_bone_index) {
1403 int current_bone_idx = bones_to_process[current_bone_index];
1404 current_bone_index++;
1405
1406 Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color;
1407
1408 Vector<int> child_bones_vector;
1409 child_bones_vector = skeleton->get_bone_children(current_bone_idx);
1410 int child_bones_size = child_bones_vector.size();
1411
1412 for (int i = 0; i < child_bones_size; i++) {
1413 // Something wrong.
1414 if (child_bones_vector[i] < 0) {
1415 continue;
1416 }
1417
1418 int child_bone_idx = child_bones_vector[i];
1419
1420 Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin;
1421 Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin;
1422 Vector3 d = (v1 - v0).normalized();
1423 real_t dist = v0.distance_to(v1);
1424
1425 // Find closest axis.
1426 int closest = -1;
1427 real_t closest_d = 0.0;
1428 for (int j = 0; j < 3; j++) {
1429 real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d));
1430 if (j == 0 || dp > closest_d) {
1431 closest = j;
1432 }
1433 }
1434
1435 // Draw bone.
1436 switch (bone_shape) {
1437 case 0: { // Wire shape.
1438 surface_tool->set_color(current_bone_color);
1439 bones[0] = current_bone_idx;
1440 surface_tool->set_bones(bones);
1441 surface_tool->set_weights(weights);
1442 surface_tool->add_vertex(v0);
1443 bones[0] = child_bone_idx;
1444 surface_tool->set_bones(bones);
1445 surface_tool->set_weights(weights);
1446 surface_tool->add_vertex(v1);
1447 } break;
1448
1449 case 1: { // Octahedron shape.
1450 Vector3 first;
1451 Vector3 points[6];
1452 int point_idx = 0;
1453 for (int j = 0; j < 3; j++) {
1454 Vector3 axis;
1455 if (first == Vector3()) {
1456 axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized();
1457 first = axis;
1458 } else {
1459 axis = d.cross(first).normalized();
1460 }
1461
1462 surface_tool->set_color(current_bone_color);
1463 for (int k = 0; k < 2; k++) {
1464 if (k == 1) {
1465 axis = -axis;
1466 }
1467 Vector3 point = v0 + d * dist * 0.2;
1468 point += axis * dist * 0.1;
1469
1470 bones[0] = current_bone_idx;
1471 surface_tool->set_bones(bones);
1472 surface_tool->set_weights(weights);
1473 surface_tool->add_vertex(v0);
1474 surface_tool->set_bones(bones);
1475 surface_tool->set_weights(weights);
1476 surface_tool->add_vertex(point);
1477
1478 surface_tool->set_bones(bones);
1479 surface_tool->set_weights(weights);
1480 surface_tool->add_vertex(point);
1481 bones[0] = child_bone_idx;
1482 surface_tool->set_bones(bones);
1483 surface_tool->set_weights(weights);
1484 surface_tool->add_vertex(v1);
1485 points[point_idx++] = point;
1486 }
1487 }
1488 surface_tool->set_color(current_bone_color);
1489 SWAP(points[1], points[2]);
1490 bones[0] = current_bone_idx;
1491 for (int j = 0; j < 6; j++) {
1492 surface_tool->set_bones(bones);
1493 surface_tool->set_weights(weights);
1494 surface_tool->add_vertex(points[j]);
1495 surface_tool->set_bones(bones);
1496 surface_tool->set_weights(weights);
1497 surface_tool->add_vertex(points[(j + 1) % 6]);
1498 }
1499 } break;
1500 }
1501
1502 // Axis as root of the bone.
1503 for (int j = 0; j < 3; j++) {
1504 bones[0] = current_bone_idx;
1505 surface_tool->set_color(axis_colors[j]);
1506 surface_tool->set_bones(bones);
1507 surface_tool->set_weights(weights);
1508 surface_tool->add_vertex(v0);
1509 surface_tool->set_bones(bones);
1510 surface_tool->set_weights(weights);
1511 surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
1512
1513 if (j == closest) {
1514 continue;
1515 }
1516 }
1517
1518 // Axis at the end of the bone children.
1519 if (i == child_bones_size - 1) {
1520 for (int j = 0; j < 3; j++) {
1521 bones[0] = child_bone_idx;
1522 surface_tool->set_color(axis_colors[j]);
1523 surface_tool->set_bones(bones);
1524 surface_tool->set_weights(weights);
1525 surface_tool->add_vertex(v1);
1526 surface_tool->set_bones(bones);
1527 surface_tool->set_weights(weights);
1528 surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
1529
1530 if (j == closest) {
1531 continue;
1532 }
1533 }
1534 }
1535
1536 // Add the bone's children to the list of bones to be processed.
1537 bones_to_process.push_back(child_bones_vector[i]);
1538 }
1539 }
1540
1541 Ref<ArrayMesh> m = surface_tool->commit();
1542 p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms()));
1543}
1544